Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/gpencil/annotate_paint.c
- This file was copied from source/blender/editors/gpencil/gpencil_paint.c.
| Show All 9 Lines | |||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| * GNU General Public License for more details. | * GNU General Public License for more details. | ||||
| * | * | ||||
| * You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
| * along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| * | * | ||||
| * The Original Code is Copyright (C) 2008, Blender Foundation, Joshua Leung | * The Original Code is Copyright (C) 2008/2018, Blender Foundation | ||||
| * This is a new part of Blender | * This is a new part of Blender | ||||
| * | * | ||||
| * Contributor(s): Joshua Leung, Antonio Vazquez | * Contributor(s): Joshua Leung | ||||
| * | * | ||||
| * ***** END GPL LICENSE BLOCK ***** | * ***** END GPL LICENSE BLOCK ***** | ||||
| */ | */ | ||||
| /** \file blender/editors/gpencil/gpencil_paint.c | /** \file blender/editors/gpencil/annotate_paint.c | ||||
| * \ingroup edgpencil | * \ingroup edgpencil | ||||
| */ | */ | ||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include <stddef.h> | #include <stddef.h> | ||||
| #include <stdlib.h> | #include <stdlib.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include <math.h> | #include <math.h> | ||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "BLI_blenlib.h" | #include "BLI_blenlib.h" | ||||
| #include "BLI_math.h" | #include "BLI_math.h" | ||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLI_rand.h" | |||||
| #include "BLI_math_geom.h" | #include "BLI_math_geom.h" | ||||
| #include "BLT_translation.h" | #include "BLT_translation.h" | ||||
| #include "PIL_time.h" | #include "PIL_time.h" | ||||
| #include "BKE_colortools.h" | #include "BKE_colortools.h" | ||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_global.h" | #include "BKE_global.h" | ||||
| #include "BKE_gpencil.h" | #include "BKE_gpencil.h" | ||||
| #include "BKE_main.h" | #include "BKE_main.h" | ||||
| #include "BKE_paint.h" | |||||
| #include "BKE_report.h" | #include "BKE_report.h" | ||||
| #include "BKE_screen.h" | #include "BKE_screen.h" | ||||
| #include "BKE_tracking.h" | #include "BKE_tracking.h" | ||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_scene_types.h" | #include "DNA_scene_types.h" | ||||
| #include "DNA_gpencil_types.h" | #include "DNA_gpencil_types.h" | ||||
| #include "DNA_brush_types.h" | |||||
| #include "DNA_windowmanager_types.h" | #include "DNA_windowmanager_types.h" | ||||
| #include "UI_view2d.h" | #include "UI_view2d.h" | ||||
| #include "ED_gpencil.h" | #include "ED_gpencil.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "ED_view3d.h" | #include "ED_view3d.h" | ||||
| #include "ED_clip.h" | #include "ED_clip.h" | ||||
| ▲ Show 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | typedef struct tGPsdata { | ||||
| float imat[4][4]; /* inverted transformation matrix applying when converting coords from screen-space | float imat[4][4]; /* inverted transformation matrix applying when converting coords from screen-space | ||||
| * to region space */ | * to region space */ | ||||
| float mat[4][4]; | float mat[4][4]; | ||||
| float custom_color[4]; /* custom color - hack for enforcing a particular color for track/mask editing */ | float custom_color[4]; /* custom color - hack for enforcing a particular color for track/mask editing */ | ||||
| void *erasercursor; /* radial cursor data for drawing eraser */ | void *erasercursor; /* radial cursor data for drawing eraser */ | ||||
| bGPDpalettecolor *palettecolor; /* current palette color */ | |||||
| bGPDbrush *brush; /* current drawing brush */ | |||||
| short straight[2]; /* 1: line horizontal, 2: line vertical, other: not defined, second element position */ | short straight[2]; /* 1: line horizontal, 2: line vertical, other: not defined, second element position */ | ||||
| int lock_axis; /* lock drawing to one axis */ | int lock_axis; /* lock drawing to one axis */ | ||||
| RNG *rng; | |||||
| short keymodifier; /* key used for invoking the operator */ | short keymodifier; /* key used for invoking the operator */ | ||||
| } tGPsdata; | } tGPsdata; | ||||
| /* ------ */ | /* ------ */ | ||||
| /* Macros for accessing sensitivity thresholds... */ | /* Macros for accessing sensitivity thresholds... */ | ||||
| /* minimum number of pixels mouse should move before new point created */ | /* minimum number of pixels mouse should move before new point created */ | ||||
| #define MIN_MANHATTEN_PX (U.gp_manhattendist) | #define MIN_MANHATTEN_PX (U.gp_manhattendist) | ||||
| Show All 24 Lines | |||||
| { | { | ||||
| if (ED_operator_regionactive(C)) { | if (ED_operator_regionactive(C)) { | ||||
| /* check if current context can support GPencil data */ | /* check if current context can support GPencil data */ | ||||
| if (ED_gpencil_data_get_pointers(C, NULL) != NULL) { | if (ED_gpencil_data_get_pointers(C, NULL) != NULL) { | ||||
| /* check if Grease Pencil isn't already running */ | /* check if Grease Pencil isn't already running */ | ||||
| if (ED_gpencil_session_active() == 0) | if (ED_gpencil_session_active() == 0) | ||||
| return 1; | return 1; | ||||
| else | else | ||||
| CTX_wm_operator_poll_msg_set(C, "Grease Pencil operator is already active"); | CTX_wm_operator_poll_msg_set(C, "Annotation operator is already active"); | ||||
| } | } | ||||
| else { | else { | ||||
| CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into"); | CTX_wm_operator_poll_msg_set(C, "Failed to find Grease Pencil data to draw into"); | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| CTX_wm_operator_poll_msg_set(C, "Active region not set"); | CTX_wm_operator_poll_msg_set(C, "Active region not set"); | ||||
| } | } | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| /* check if projecting strokes into 3d-geometry in the 3D-View */ | /* check if projecting strokes into 3d-geometry in the 3D-View */ | ||||
| static bool gpencil_project_check(tGPsdata *p) | static bool gpencil_project_check(tGPsdata *p) | ||||
| { | { | ||||
| bGPdata *gpd = p->gpd; | bGPdata *gpd = p->gpd; | ||||
| return ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) && (*p->align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE))); | return ((gpd->runtime.sbuffer_sflag & GP_STROKE_3DSPACE) && (*p->align_flag & (GP_PROJECT_DEPTH_VIEW | GP_PROJECT_DEPTH_STROKE))); | ||||
| } | } | ||||
| /* ******************************************* */ | /* ******************************************* */ | ||||
| /* Calculations/Conversions */ | /* Calculations/Conversions */ | ||||
| /* Utilities --------------------------------- */ | /* Utilities --------------------------------- */ | ||||
| /* get the reference point for stroke-point conversions */ | /* get the reference point for stroke-point conversions */ | ||||
| static void gp_get_3d_reference(tGPsdata *p, float vec[3]) | static void gp_get_3d_reference(tGPsdata *p, float vec[3]) | ||||
| { | { | ||||
| View3D *v3d = p->sa->spacedata.first; | View3D *v3d = p->sa->spacedata.first; | ||||
| const float *fp = ED_view3d_cursor3d_get(p->scene, v3d)->location; | const float *fp = ED_view3d_cursor3d_get(p->scene, v3d)->location; | ||||
| /* the reference point used depends on the owner... */ | |||||
| #if 0 /* XXX: disabled for now, since we can't draw relative to the owner yet */ | |||||
| if (p->ownerPtr.type == &RNA_Object) { | |||||
| Object *ob = (Object *)p->ownerPtr.data; | |||||
| /* active Object | |||||
| * - use relative distance of 3D-cursor from object center | |||||
| */ | |||||
| sub_v3_v3v3(vec, fp, ob->loc); | |||||
| } | |||||
| else | |||||
| #endif | |||||
| { | |||||
| /* use 3D-cursor */ | /* use 3D-cursor */ | ||||
| copy_v3_v3(vec, fp); | copy_v3_v3(vec, fp); | ||||
| } | } | ||||
| } | |||||
| /* Stroke Editing ---------------------------- */ | /* Stroke Editing ---------------------------- */ | ||||
| /* check if the current mouse position is suitable for adding a new point */ | /* check if the current mouse position is suitable for adding a new point */ | ||||
| static bool gp_stroke_filtermval(tGPsdata *p, const int mval[2], int pmval[2]) | static bool gp_stroke_filtermval(tGPsdata *p, const int mval[2], int pmval[2]) | ||||
| { | { | ||||
| int dx = abs(mval[0] - pmval[0]); | int dx = abs(mval[0] - pmval[0]); | ||||
| int dy = abs(mval[1] - pmval[1]); | int dy = abs(mval[1] - pmval[1]); | ||||
| /* if buffer is empty, just let this go through (i.e. so that dots will work) */ | /* if buffer is empty, just let this go through (i.e. so that dots will work) */ | ||||
| if (p->gpd->sbuffer_size == 0) | if (p->gpd->runtime.sbuffer_size == 0) | ||||
| return true; | return true; | ||||
| /* check if mouse moved at least certain distance on both axes (best case) | /* check if mouse moved at least certain distance on both axes (best case) | ||||
| * - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand | * - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand | ||||
| */ | */ | ||||
| else if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX)) | else if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX)) | ||||
| return true; | return true; | ||||
| ▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | |||||
| static void gp_reproject_toplane(tGPsdata *p, bGPDstroke *gps) | static void gp_reproject_toplane(tGPsdata *p, bGPDstroke *gps) | ||||
| { | { | ||||
| bGPdata *gpd = p->gpd; | bGPdata *gpd = p->gpd; | ||||
| float origin[3]; | float origin[3]; | ||||
| float cursor[3]; | float cursor[3]; | ||||
| RegionView3D *rv3d = p->ar->regiondata; | RegionView3D *rv3d = p->ar->regiondata; | ||||
| /* verify the stroke mode is CURSOR 3d space mode */ | /* verify the stroke mode is CURSOR 3d space mode */ | ||||
| if ((gpd->sbuffer_sflag & GP_STROKE_3DSPACE) == 0) { | if ((gpd->runtime.sbuffer_sflag & GP_STROKE_3DSPACE) == 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) { | if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| if ((*p->align_flag & GP_PROJECT_DEPTH_VIEW) || (*p->align_flag & GP_PROJECT_DEPTH_STROKE)) { | if ((*p->align_flag & GP_PROJECT_DEPTH_VIEW) || (*p->align_flag & GP_PROJECT_DEPTH_STROKE)) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* get 3d cursor and set origin for locked axis only. Uses axis-1 because the enum for XYZ start with 1 */ | /* get 3d cursor and set origin for locked axis only. Uses axis-1 because the enum for XYZ start with 1 */ | ||||
| gp_get_3d_reference(p, cursor); | gp_get_3d_reference(p, cursor); | ||||
| zero_v3(origin); | zero_v3(origin); | ||||
| origin[p->lock_axis - 1] = cursor[p->lock_axis - 1]; | origin[p->lock_axis - 1] = cursor[p->lock_axis - 1]; | ||||
| gp_project_points_to_plane(rv3d, gps, origin, p->lock_axis - 1); | gp_project_points_to_plane(rv3d, gps, origin, p->lock_axis - 1); | ||||
| } | } | ||||
| /* convert screen-coordinates to buffer-coordinates */ | /* convert screen-coordinates to buffer-coordinates */ | ||||
| /* XXX this method needs a total overhaul! */ | /* XXX this method needs a total overhaul! */ | ||||
| static void gp_stroke_convertcoords(tGPsdata *p, const int mval[2], float out[3], float *depth) | static void gp_stroke_convertcoords(tGPsdata *p, const int mval[2], float out[3], float *depth) | ||||
| { | { | ||||
| bGPdata *gpd = p->gpd; | bGPdata *gpd = p->gpd; | ||||
| /* in 3d-space - pt->x/y/z are 3 side-by-side floats */ | /* in 3d-space - pt->x/y/z are 3 side-by-side floats */ | ||||
| if (gpd->sbuffer_sflag & GP_STROKE_3DSPACE) { | if (gpd->runtime.sbuffer_sflag & GP_STROKE_3DSPACE) { | ||||
| if (gpencil_project_check(p) && (ED_view3d_autodist_simple(p->ar, mval, out, 0, depth))) { | if (gpencil_project_check(p) && (ED_view3d_autodist_simple(p->ar, mval, out, 0, depth))) { | ||||
| /* projecting onto 3D-Geometry | /* projecting onto 3D-Geometry | ||||
| * - nothing more needs to be done here, since view_autodist_simple() has already done it | * - nothing more needs to be done here, since view_autodist_simple() has already done it | ||||
| */ | */ | ||||
| } | } | ||||
| else { | else { | ||||
| float mval_prj[2]; | float mval_prj[2]; | ||||
| float rvec[3], dvec[3]; | float rvec[3], dvec[3]; | ||||
| Show All 19 Lines | else { | ||||
| } | } | ||||
| else { | else { | ||||
| zero_v3(out); | zero_v3(out); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* 2d - on 'canvas' (assume that p->v2d is set) */ | /* 2d - on 'canvas' (assume that p->v2d is set) */ | ||||
| else if ((gpd->sbuffer_sflag & GP_STROKE_2DSPACE) && (p->v2d)) { | else if ((gpd->runtime.sbuffer_sflag & GP_STROKE_2DSPACE) && (p->v2d)) { | ||||
| UI_view2d_region_to_view(p->v2d, mval[0], mval[1], &out[0], &out[1]); | UI_view2d_region_to_view(p->v2d, mval[0], mval[1], &out[0], &out[1]); | ||||
| mul_v3_m4v3(out, p->imat, out); | mul_v3_m4v3(out, p->imat, out); | ||||
| } | } | ||||
| /* 2d - relative to screen (viewport area) */ | /* 2d - relative to screen (viewport area) */ | ||||
| else { | else { | ||||
| if (p->subrect == NULL) { /* normal 3D view */ | if (p->subrect == NULL) { /* normal 3D view */ | ||||
| out[0] = (float)(mval[0]) / (float)(p->ar->winx) * 100; | out[0] = (float)(mval[0]) / (float)(p->ar->winx) * 100; | ||||
| out[1] = (float)(mval[1]) / (float)(p->ar->winy) * 100; | out[1] = (float)(mval[1]) / (float)(p->ar->winy) * 100; | ||||
| } | } | ||||
| else { /* camera view, use subrect */ | else { /* camera view, use subrect */ | ||||
| out[0] = ((mval[0] - p->subrect->xmin) / BLI_rctf_size_x(p->subrect)) * 100; | out[0] = ((mval[0] - p->subrect->xmin) / BLI_rctf_size_x(p->subrect)) * 100; | ||||
| out[1] = ((mval[1] - p->subrect->ymin) / BLI_rctf_size_y(p->subrect)) * 100; | out[1] = ((mval[1] - p->subrect->ymin) / BLI_rctf_size_y(p->subrect)) * 100; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* apply jitter to stroke */ | |||||
| static void gp_brush_jitter( | |||||
| bGPdata *gpd, bGPDbrush *brush, tGPspoint *pt, const int mval[2], int r_mval[2], RNG *rng) | |||||
| { | |||||
| float pressure = pt->pressure; | |||||
| float tmp_pressure = pt->pressure; | |||||
| if (brush->draw_jitter > 0.0f) { | |||||
| float curvef = curvemapping_evaluateF(brush->cur_jitter, 0, pressure); | |||||
| tmp_pressure = curvef * brush->draw_sensitivity; | |||||
| } | |||||
| const float exfactor = (brush->draw_jitter + 2.0f) * (brush->draw_jitter + 2.0f); /* exponential value */ | |||||
| const float fac = BLI_rng_get_float(rng) * exfactor * tmp_pressure; | |||||
| /* Jitter is applied perpendicular to the mouse movement vector (2D space) */ | |||||
| float mvec[2], svec[2]; | |||||
| /* mouse movement in ints -> floats */ | |||||
| if (gpd->sbuffer_size > 1) { | |||||
| mvec[0] = (float)(mval[0] - (pt - 1)->x); | |||||
| mvec[1] = (float)(mval[1] - (pt - 1)->y); | |||||
| normalize_v2(mvec); | |||||
| } | |||||
| else { | |||||
| mvec[0] = 0.0f; | |||||
| mvec[1] = 0.0f; | |||||
| } | |||||
| /* rotate mvec by 90 degrees... */ | |||||
| svec[0] = -mvec[1]; | |||||
| svec[1] = mvec[0]; | |||||
| /* scale the displacement by the random, and apply */ | |||||
| if (BLI_rng_get_float(rng) > 0.5f) { | |||||
| mul_v2_fl(svec, -fac); | |||||
| } | |||||
| else { | |||||
| mul_v2_fl(svec, fac); | |||||
| } | |||||
| r_mval[0] = mval[0] + svec[0]; | |||||
| r_mval[1] = mval[1] + svec[1]; | |||||
| } | |||||
| /* apply pressure change depending of the angle of the stroke to simulate a pen with shape */ | |||||
| static void gp_brush_angle(bGPdata *gpd, bGPDbrush *brush, tGPspoint *pt, const int mval[2]) | |||||
| { | |||||
| float mvec[2]; | |||||
| float sen = brush->draw_angle_factor; /* sensitivity */; | |||||
| float fac; | |||||
| float mpressure; | |||||
| float angle = brush->draw_angle; /* default angle of brush in radians */; | |||||
| float v0[2] = { cos(angle), sin(angle) }; /* angle vector of the brush with full thickness */ | |||||
| /* Apply to first point (only if there are 2 points because before no data to do it ) */ | |||||
| if (gpd->sbuffer_size == 1) { | |||||
| mvec[0] = (float)(mval[0] - (pt - 1)->x); | |||||
| mvec[1] = (float)(mval[1] - (pt - 1)->y); | |||||
| normalize_v2(mvec); | |||||
| /* uses > 1.0f to get a smooth transition in first point */ | |||||
| fac = 1.4f - fabs(dot_v2v2(v0, mvec)); /* 0.0 to 1.0 */ | |||||
| (pt - 1)->pressure = (pt - 1)->pressure - (sen * fac); | |||||
| CLAMP((pt - 1)->pressure, GPENCIL_ALPHA_OPACITY_THRESH, 1.0f); | |||||
| } | |||||
| /* apply from second point */ | |||||
| if (gpd->sbuffer_size >= 1) { | |||||
| mvec[0] = (float)(mval[0] - (pt - 1)->x); | |||||
| mvec[1] = (float)(mval[1] - (pt - 1)->y); | |||||
| normalize_v2(mvec); | |||||
| fac = 1.0f - fabs(dot_v2v2(v0, mvec)); /* 0.0 to 1.0 */ | |||||
| /* interpolate with previous point for smoother transitions */ | |||||
| mpressure = interpf(pt->pressure - (sen * fac), (pt - 1)->pressure, 0.3f); | |||||
| pt->pressure = mpressure; | |||||
| CLAMP(pt->pressure, GPENCIL_ALPHA_OPACITY_THRESH, 1.0f); | |||||
| } | |||||
| } | |||||
| /* add current stroke-point to buffer (returns whether point was successfully added) */ | /* add current stroke-point to buffer (returns whether point was successfully added) */ | ||||
| static short gp_stroke_addpoint( | static short gp_stroke_addpoint( | ||||
| tGPsdata *p, const int mval[2], float pressure, double curtime) | tGPsdata *p, const int mval[2], float pressure, double curtime) | ||||
| { | { | ||||
| bGPdata *gpd = p->gpd; | bGPdata *gpd = p->gpd; | ||||
| bGPDbrush *brush = p->brush; | |||||
| tGPspoint *pt; | tGPspoint *pt; | ||||
| ToolSettings *ts = p->scene->toolsettings; | ToolSettings *ts = p->scene->toolsettings; | ||||
| /* check painting mode */ | /* check painting mode */ | ||||
| if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { | if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { | ||||
| /* straight lines only - i.e. only store start and end point in buffer */ | /* straight lines only - i.e. only store start and end point in buffer */ | ||||
| if (gpd->sbuffer_size == 0) { | if (gpd->runtime.sbuffer_size == 0) { | ||||
| /* first point in buffer (start point) */ | /* first point in buffer (start point) */ | ||||
| pt = (tGPspoint *)(gpd->sbuffer); | pt = (tGPspoint *)(gpd->runtime.sbuffer); | ||||
| /* store settings */ | /* store settings */ | ||||
| copy_v2_v2_int(&pt->x, mval); | copy_v2_v2_int(&pt->x, mval); | ||||
| pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ | pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ | ||||
| pt->strength = 1.0f; | pt->strength = 1.0f; | ||||
| pt->time = (float)(curtime - p->inittime); | pt->time = (float)(curtime - p->inittime); | ||||
| /* increment buffer size */ | /* increment buffer size */ | ||||
| gpd->sbuffer_size++; | gpd->runtime.sbuffer_size++; | ||||
| } | } | ||||
| else { | else { | ||||
| /* just reset the endpoint to the latest value | /* just reset the endpoint to the latest value | ||||
| * - assume that pointers for this are always valid... | * - assume that pointers for this are always valid... | ||||
| */ | */ | ||||
| pt = ((tGPspoint *)(gpd->sbuffer) + 1); | pt = ((tGPspoint *)(gpd->runtime.sbuffer) + 1); | ||||
| /* store settings */ | /* store settings */ | ||||
| copy_v2_v2_int(&pt->x, mval); | copy_v2_v2_int(&pt->x, mval); | ||||
| pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ | pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ | ||||
| pt->strength = 1.0f; | pt->strength = 1.0f; | ||||
| pt->time = (float)(curtime - p->inittime); | pt->time = (float)(curtime - p->inittime); | ||||
| /* now the buffer has 2 points (and shouldn't be allowed to get any larger) */ | /* now the buffer has 2 points (and shouldn't be allowed to get any larger) */ | ||||
| gpd->sbuffer_size = 2; | gpd->runtime.sbuffer_size = 2; | ||||
| } | } | ||||
| /* can keep carrying on this way :) */ | /* can keep carrying on this way :) */ | ||||
| return GP_STROKEADD_NORMAL; | return GP_STROKEADD_NORMAL; | ||||
| } | } | ||||
| else if (p->paintmode == GP_PAINTMODE_DRAW) { /* normal drawing */ | else if (p->paintmode == GP_PAINTMODE_DRAW) { /* normal drawing */ | ||||
| /* check if still room in buffer */ | /* check if still room in buffer */ | ||||
| if (gpd->sbuffer_size >= GP_STROKE_BUFFER_MAX) | if (gpd->runtime.sbuffer_size >= GP_STROKE_BUFFER_MAX) | ||||
| return GP_STROKEADD_OVERFLOW; | return GP_STROKEADD_OVERFLOW; | ||||
| /* get pointer to destination point */ | /* get pointer to destination point */ | ||||
| pt = ((tGPspoint *)(gpd->sbuffer) + gpd->sbuffer_size); | pt = ((tGPspoint *)(gpd->runtime.sbuffer) + gpd->runtime.sbuffer_size); | ||||
| /* store settings */ | /* store settings */ | ||||
| /* pressure */ | |||||
| if (brush->flag & GP_BRUSH_USE_PRESSURE) { | |||||
| float curvef = curvemapping_evaluateF(brush->cur_sensitivity, 0, pressure); | |||||
| pt->pressure = curvef * brush->draw_sensitivity; | |||||
| } | |||||
| else { | |||||
| pt->pressure = 1.0f; | |||||
| } | |||||
| /* Apply jitter to position */ | |||||
| if (brush->draw_jitter > 0.0f) { | |||||
| int r_mval[2]; | |||||
| gp_brush_jitter(gpd, brush, pt, mval, r_mval, p->rng); | |||||
| copy_v2_v2_int(&pt->x, r_mval); | |||||
| } | |||||
| else { | |||||
| copy_v2_v2_int(&pt->x, mval); | copy_v2_v2_int(&pt->x, mval); | ||||
| } | pt->pressure = pressure; | ||||
| /* apply randomness to pressure */ | pt->strength = 1.0f; /* unused for annotations, but initialise for easier conversions to GP Object */ | ||||
| if ((brush->draw_random_press > 0.0f) && (brush->flag & GP_BRUSH_USE_RANDOM_PRESSURE)) { | |||||
| float curvef = curvemapping_evaluateF(brush->cur_sensitivity, 0, pressure); | |||||
| float tmp_pressure = curvef * brush->draw_sensitivity; | |||||
| if (BLI_rng_get_float(p->rng) > 0.5f) { | |||||
| pt->pressure -= tmp_pressure * brush->draw_random_press * BLI_rng_get_float(p->rng); | |||||
| } | |||||
| else { | |||||
| pt->pressure += tmp_pressure * brush->draw_random_press * BLI_rng_get_float(p->rng); | |||||
| } | |||||
| CLAMP(pt->pressure, GPENCIL_STRENGTH_MIN, 1.0f); | |||||
| } | |||||
| /* apply angle of stroke to brush size */ | |||||
| if (brush->draw_angle_factor > 0.0f) { | |||||
| gp_brush_angle(gpd, brush, pt, mval); | |||||
| } | |||||
| /* color strength */ | |||||
| if (brush->flag & GP_BRUSH_USE_STENGTH_PRESSURE) { | |||||
| float curvef = curvemapping_evaluateF(brush->cur_strength, 0, pressure); | |||||
| float tmp_pressure = curvef * brush->draw_sensitivity; | |||||
| pt->strength = tmp_pressure * brush->draw_strength; | |||||
| } | |||||
| else { | |||||
| pt->strength = brush->draw_strength; | |||||
| } | |||||
| CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | |||||
| /* apply randomness to color strength */ | |||||
| if ((brush->draw_random_press > 0.0f) && (brush->flag & GP_BRUSH_USE_RANDOM_STRENGTH)) { | |||||
| if (BLI_rng_get_float(p->rng) > 0.5f) { | |||||
| pt->strength -= pt->strength * brush->draw_random_press * BLI_rng_get_float(p->rng); | |||||
| } | |||||
| else { | |||||
| pt->strength += pt->strength * brush->draw_random_press * BLI_rng_get_float(p->rng); | |||||
| } | |||||
| CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | |||||
| } | |||||
| /* point time */ | /* point time */ | ||||
| pt->time = (float)(curtime - p->inittime); | pt->time = (float)(curtime - p->inittime); | ||||
| /* increment counters */ | /* increment counters */ | ||||
| gpd->sbuffer_size++; | gpd->runtime.sbuffer_size++; | ||||
| /* check if another operation can still occur */ | /* check if another operation can still occur */ | ||||
| if (gpd->sbuffer_size == GP_STROKE_BUFFER_MAX) | if (gpd->runtime.sbuffer_size == GP_STROKE_BUFFER_MAX) | ||||
| return GP_STROKEADD_FULL; | return GP_STROKEADD_FULL; | ||||
| else | else | ||||
| return GP_STROKEADD_NORMAL; | return GP_STROKEADD_NORMAL; | ||||
| } | } | ||||
| else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { | else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { | ||||
| #if 0 | |||||
| bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); | bGPDlayer *gpl = BKE_gpencil_layer_getactive(gpd); | ||||
| #endif | |||||
| /* get pointer to destination point */ | /* get pointer to destination point */ | ||||
| pt = (tGPspoint *)(gpd->sbuffer); | pt = (tGPspoint *)(gpd->runtime.sbuffer); | ||||
| /* store settings */ | /* store settings */ | ||||
| copy_v2_v2_int(&pt->x, mval); | copy_v2_v2_int(&pt->x, mval); | ||||
| pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ | pt->pressure = 1.0f; /* T44932 - Pressure vals are unreliable, so ignore for now */ | ||||
| pt->strength = 1.0f; | pt->strength = 1.0f; | ||||
| pt->time = (float)(curtime - p->inittime); | pt->time = (float)(curtime - p->inittime); | ||||
| /* if there's stroke for this poly line session add (or replace last) point | /* if there's stroke for this poly line session add (or replace last) point | ||||
| * to stroke. This allows to draw lines more interactively (see new segment | * to stroke. This allows to draw lines more interactively (see new segment | ||||
| * during mouse slide, e.g.) | * during mouse slide, e.g.) | ||||
| */ | */ | ||||
| if (gp_stroke_added_check(p)) { | if (gp_stroke_added_check(p)) { | ||||
| bGPDstroke *gps = p->gpf->strokes.last; | bGPDstroke *gps = p->gpf->strokes.last; | ||||
| bGPDspoint *pts; | bGPDspoint *pts; | ||||
| /* first time point is adding to temporary buffer -- need to allocate new point in stroke */ | /* first time point is adding to temporary buffer -- need to allocate new point in stroke */ | ||||
| if (gpd->sbuffer_size == 0) { | if (gpd->runtime.sbuffer_size == 0) { | ||||
| gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); | gps->points = MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)); | ||||
| gps->totpoints++; | gps->totpoints++; | ||||
| } | } | ||||
| pts = &gps->points[gps->totpoints - 1]; | pts = &gps->points[gps->totpoints - 1]; | ||||
| /* special case for poly lines: normally, | /* special case for poly lines: normally, | ||||
| * depth is needed only when creating new stroke from buffer, | * depth is needed only when creating new stroke from buffer, | ||||
| Show All 9 Lines | if (gp_stroke_added_check(p)) { | ||||
| } | } | ||||
| /* convert screen-coordinates to appropriate coordinates (and store them) */ | /* convert screen-coordinates to appropriate coordinates (and store them) */ | ||||
| gp_stroke_convertcoords(p, &pt->x, &pts->x, NULL); | gp_stroke_convertcoords(p, &pt->x, &pts->x, NULL); | ||||
| /* if axis locked, reproject to plane locked (only in 3d space) */ | /* if axis locked, reproject to plane locked (only in 3d space) */ | ||||
| if (p->lock_axis > GP_LOCKAXIS_NONE) { | if (p->lock_axis > GP_LOCKAXIS_NONE) { | ||||
| gp_reproject_toplane(p, gps); | gp_reproject_toplane(p, gps); | ||||
| } | } | ||||
| /* if parented change position relative to parent object */ | |||||
| if (gpl->parent != NULL) { | |||||
| gp_apply_parent_point(gpl, pts); | |||||
| } | |||||
| /* copy pressure and time */ | /* copy pressure and time */ | ||||
| pts->pressure = pt->pressure; | pts->pressure = pt->pressure; | ||||
| pts->strength = pt->strength; | pts->strength = pt->strength; | ||||
| pts->time = pt->time; | pts->time = pt->time; | ||||
| /* force fill recalc */ | /* force fill recalc */ | ||||
| gps->flag |= GP_STROKE_RECALC_CACHES; | gps->flag |= GP_STROKE_RECALC_CACHES; | ||||
| } | } | ||||
| /* increment counters */ | /* increment counters */ | ||||
| if (gpd->sbuffer_size == 0) | if (gpd->runtime.sbuffer_size == 0) | ||||
| gpd->sbuffer_size++; | gpd->runtime.sbuffer_size++; | ||||
| return GP_STROKEADD_NORMAL; | return GP_STROKEADD_NORMAL; | ||||
| } | } | ||||
| /* return invalid state for now... */ | /* return invalid state for now... */ | ||||
| return GP_STROKEADD_INVALID; | return GP_STROKEADD_INVALID; | ||||
| } | } | ||||
| /* simplify a stroke (in buffer) before storing it | /* simplify a stroke (in buffer) before storing it | ||||
| * - applies a reverse Chaikin filter | * - applies a reverse Chaikin filter | ||||
| * - code adapted from etch-a-ton branch | * - code adapted from etch-a-ton branch | ||||
| */ | */ | ||||
| static void gp_stroke_simplify(tGPsdata *p) | static void gp_stroke_simplify(tGPsdata *p) | ||||
| { | { | ||||
| bGPdata *gpd = p->gpd; | bGPdata *gpd = p->gpd; | ||||
| tGPspoint *old_points = (tGPspoint *)gpd->sbuffer; | tGPspoint *old_points = (tGPspoint *)gpd->runtime.sbuffer; | ||||
| short num_points = gpd->sbuffer_size; | short num_points = gpd->runtime.sbuffer_size; | ||||
| short flag = gpd->sbuffer_sflag; | short flag = gpd->runtime.sbuffer_sflag; | ||||
| short i, j; | short i, j; | ||||
| /* only simplify if simplification is enabled, and we're not doing a straight line */ | /* only simplify if simplification is enabled, and we're not doing a straight line */ | ||||
| if (!(U.gp_settings & GP_PAINT_DOSIMPLIFY) || (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT)) | if (!(U.gp_settings & GP_PAINT_DOSIMPLIFY) || (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT)) | ||||
| return; | return; | ||||
| /* don't simplify if less than 4 points in buffer */ | /* don't simplify if less than 4 points in buffer */ | ||||
| if ((num_points <= 4) || (old_points == NULL)) | if ((num_points <= 4) || (old_points == NULL)) | ||||
| return; | return; | ||||
| /* clear buffer (but don't free mem yet) so that we can write to it | /* clear buffer (but don't free mem yet) so that we can write to it | ||||
| * - firstly set sbuffer to NULL, so a new one is allocated | * - firstly set sbuffer to NULL, so a new one is allocated | ||||
| * - secondly, reset flag after, as it gets cleared auto | * - secondly, reset flag after, as it gets cleared auto | ||||
| */ | */ | ||||
| gpd->sbuffer = NULL; | gpd->runtime.sbuffer = NULL; | ||||
| gp_session_validatebuffer(p); | gp_session_validatebuffer(p); | ||||
| gpd->sbuffer_sflag = flag; | gpd->runtime.sbuffer_sflag = flag; | ||||
| /* macro used in loop to get position of new point | /* macro used in loop to get position of new point | ||||
| * - used due to the mixture of datatypes in use here | * - used due to the mixture of datatypes in use here | ||||
| */ | */ | ||||
| #define GP_SIMPLIFY_AVPOINT(offs, sfac) \ | #define GP_SIMPLIFY_AVPOINT(offs, sfac) \ | ||||
| { \ | { \ | ||||
| co[0] += (float)(old_points[offs].x * sfac); \ | co[0] += (float)(old_points[offs].x * sfac); \ | ||||
| co[1] += (float)(old_points[offs].y * sfac); \ | co[1] += (float)(old_points[offs].y * sfac); \ | ||||
| Show All 40 Lines | |||||
| /* make a new stroke from the buffer data */ | /* make a new stroke from the buffer data */ | ||||
| static void gp_stroke_newfrombuffer(tGPsdata *p) | static void gp_stroke_newfrombuffer(tGPsdata *p) | ||||
| { | { | ||||
| bGPdata *gpd = p->gpd; | bGPdata *gpd = p->gpd; | ||||
| bGPDlayer *gpl = p->gpl; | bGPDlayer *gpl = p->gpl; | ||||
| bGPDstroke *gps; | bGPDstroke *gps; | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| tGPspoint *ptc; | tGPspoint *ptc; | ||||
| bGPDbrush *brush = p->brush; | |||||
| ToolSettings *ts = p->scene->toolsettings; | ToolSettings *ts = p->scene->toolsettings; | ||||
| int i, totelem; | int i, totelem; | ||||
| /* since strokes are so fine, when using their depth we need a margin otherwise they might get missed */ | /* since strokes are so fine, when using their depth we need a margin otherwise they might get missed */ | ||||
| int depth_margin = (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 4 : 0; | int depth_margin = (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 4 : 0; | ||||
| /* get total number of points to allocate space for | /* get total number of points to allocate space for | ||||
| * - drawing straight-lines only requires the endpoints | * - drawing straight-lines only requires the endpoints | ||||
| */ | */ | ||||
| if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) | if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) | ||||
| totelem = (gpd->sbuffer_size >= 2) ? 2 : gpd->sbuffer_size; | totelem = (gpd->runtime.sbuffer_size >= 2) ? 2 : gpd->runtime.sbuffer_size; | ||||
| else | else | ||||
| totelem = gpd->sbuffer_size; | totelem = gpd->runtime.sbuffer_size; | ||||
| /* exit with error if no valid points from this stroke */ | /* exit with error if no valid points from this stroke */ | ||||
| if (totelem == 0) { | if (totelem == 0) { | ||||
| if (G.debug & G_DEBUG) | if (G.debug & G_DEBUG) | ||||
| printf("Error: No valid points in stroke buffer to convert (tot=%d)\n", gpd->sbuffer_size); | printf("Error: No valid points in stroke buffer to convert (tot=%d)\n", gpd->runtime.sbuffer_size); | ||||
| return; | return; | ||||
| } | } | ||||
| /* special case for poly line -- for already added stroke during session | /* special case for poly line -- for already added stroke during session | ||||
| * coordinates are getting added to stroke immediately to allow more | * coordinates are getting added to stroke immediately to allow more | ||||
| * interactive behavior | * interactive behavior | ||||
| */ | */ | ||||
| if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { | if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { | ||||
| if (gp_stroke_added_check(p)) { | if (gp_stroke_added_check(p)) { | ||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| /* allocate memory for a new stroke */ | /* allocate memory for a new stroke */ | ||||
| gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke"); | gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke"); | ||||
| /* copy appropriate settings for stroke */ | /* copy appropriate settings for stroke */ | ||||
| gps->totpoints = totelem; | gps->totpoints = totelem; | ||||
| gps->thickness = brush->thickness; | gps->thickness = gpl->thickness; | ||||
| gps->flag = gpd->sbuffer_sflag; | gps->flag = gpd->runtime.sbuffer_sflag; | ||||
| gps->inittime = p->inittime; | gps->inittime = p->inittime; | ||||
| /* enable recalculation flag by default (only used if hq fill) */ | /* enable recalculation flag by default (only used if hq fill) */ | ||||
| gps->flag |= GP_STROKE_RECALC_CACHES; | gps->flag |= GP_STROKE_RECALC_CACHES; | ||||
| /* allocate enough memory for a continuous array for storage points */ | /* allocate enough memory for a continuous array for storage points */ | ||||
| int sublevel = brush->sublevel; | gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); | ||||
| int new_totpoints = gps->totpoints; | |||||
| for (i = 0; i < sublevel; i++) { | |||||
| new_totpoints += new_totpoints - 1; | |||||
| } | |||||
| gps->points = MEM_callocN(sizeof(bGPDspoint) * new_totpoints, "gp_stroke_points"); | |||||
| /* initialize triangle memory to dummy data */ | |||||
| gps->triangles = MEM_callocN(sizeof(bGPDtriangle), "GP Stroke triangulation"); | |||||
| gps->flag |= GP_STROKE_RECALC_CACHES; | |||||
| gps->tot_triangles = 0; | gps->tot_triangles = 0; | ||||
| /* set pointer to first non-initialized point */ | /* set pointer to first non-initialized point */ | ||||
| pt = gps->points + (gps->totpoints - totelem); | pt = gps->points + (gps->totpoints - totelem); | ||||
| /* copy points from the buffer to the stroke */ | /* copy points from the buffer to the stroke */ | ||||
| if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { | if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) { | ||||
| /* straight lines only -> only endpoints */ | /* straight lines only -> only endpoints */ | ||||
| { | { | ||||
| /* first point */ | /* first point */ | ||||
| ptc = gpd->sbuffer; | ptc = gpd->runtime.sbuffer; | ||||
| /* convert screen-coordinates to appropriate coordinates (and store them) */ | /* convert screen-coordinates to appropriate coordinates (and store them) */ | ||||
| gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); | gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); | ||||
| /* if axis locked, reproject to plane locked (only in 3d space) */ | /* if axis locked, reproject to plane locked (only in 3d space) */ | ||||
| if (p->lock_axis > GP_LOCKAXIS_NONE) { | if (p->lock_axis > GP_LOCKAXIS_NONE) { | ||||
| gp_reproject_toplane(p, gps); | gp_reproject_toplane(p, gps); | ||||
| } | } | ||||
| /* if parented change position relative to parent object */ | |||||
| if (gpl->parent != NULL) { | |||||
| gp_apply_parent_point(gpl, pt); | |||||
| } | |||||
| /* copy pressure and time */ | /* copy pressure and time */ | ||||
| pt->pressure = ptc->pressure; | pt->pressure = ptc->pressure; | ||||
| pt->strength = ptc->strength; | pt->strength = ptc->strength; | ||||
| CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | ||||
| pt->time = ptc->time; | pt->time = ptc->time; | ||||
| pt++; | pt++; | ||||
| } | } | ||||
| if (totelem == 2) { | if (totelem == 2) { | ||||
| /* last point if applicable */ | /* last point if applicable */ | ||||
| ptc = ((tGPspoint *)gpd->sbuffer) + (gpd->sbuffer_size - 1); | ptc = ((tGPspoint *)gpd->runtime.sbuffer) + (gpd->runtime.sbuffer_size - 1); | ||||
| /* convert screen-coordinates to appropriate coordinates (and store them) */ | /* convert screen-coordinates to appropriate coordinates (and store them) */ | ||||
| gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); | gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); | ||||
| /* if axis locked, reproject to plane locked (only in 3d space) */ | /* if axis locked, reproject to plane locked (only in 3d space) */ | ||||
| if (p->lock_axis > GP_LOCKAXIS_NONE) { | if (p->lock_axis > GP_LOCKAXIS_NONE) { | ||||
| gp_reproject_toplane(p, gps); | gp_reproject_toplane(p, gps); | ||||
| } | } | ||||
| /* if parented change position relative to parent object */ | |||||
| if (gpl->parent != NULL) { | |||||
| gp_apply_parent_point(gpl, pt); | |||||
| } | |||||
| /* copy pressure and time */ | /* copy pressure and time */ | ||||
| pt->pressure = ptc->pressure; | pt->pressure = ptc->pressure; | ||||
| pt->strength = ptc->strength; | pt->strength = ptc->strength; | ||||
| CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | ||||
| pt->time = ptc->time; | pt->time = ptc->time; | ||||
| } | } | ||||
| } | } | ||||
| else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { | else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) { | ||||
| /* first point */ | /* first point */ | ||||
| ptc = gpd->sbuffer; | ptc = gpd->runtime.sbuffer; | ||||
| /* convert screen-coordinates to appropriate coordinates (and store them) */ | /* convert screen-coordinates to appropriate coordinates (and store them) */ | ||||
| gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); | gp_stroke_convertcoords(p, &ptc->x, &pt->x, NULL); | ||||
| /* if axis locked, reproject to plane locked (only in 3d space) */ | /* if axis locked, reproject to plane locked (only in 3d space) */ | ||||
| if (p->lock_axis > GP_LOCKAXIS_NONE) { | if (p->lock_axis > GP_LOCKAXIS_NONE) { | ||||
| gp_reproject_toplane(p, gps); | gp_reproject_toplane(p, gps); | ||||
| } | } | ||||
| /* if parented change position relative to parent object */ | |||||
| if (gpl->parent != NULL) { | |||||
| gp_apply_parent_point(gpl, pt); | |||||
| } | |||||
| /* copy pressure and time */ | /* copy pressure and time */ | ||||
| pt->pressure = ptc->pressure; | pt->pressure = ptc->pressure; | ||||
| pt->strength = ptc->strength; | pt->strength = ptc->strength; | ||||
| CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | |||||
| pt->time = ptc->time; | pt->time = ptc->time; | ||||
| } | } | ||||
| else { | else { | ||||
| float *depth_arr = NULL; | float *depth_arr = NULL; | ||||
| /* get an array of depths, far depths are blended */ | /* get an array of depths, far depths are blended */ | ||||
| if (gpencil_project_check(p)) { | if (gpencil_project_check(p)) { | ||||
| int mval[2], mval_prev[2] = { 0 }; | int mval[2], mval_prev[2] = { 0 }; | ||||
| int interp_depth = 0; | int interp_depth = 0; | ||||
| int found_depth = 0; | int found_depth = 0; | ||||
| depth_arr = MEM_mallocN(sizeof(float) * gpd->sbuffer_size, "depth_points"); | depth_arr = MEM_mallocN(sizeof(float) * gpd->runtime.sbuffer_size, "depth_points"); | ||||
| for (i = 0, ptc = gpd->sbuffer; i < gpd->sbuffer_size; i++, ptc++, pt++) { | for (i = 0, ptc = gpd->runtime.sbuffer; i < gpd->runtime.sbuffer_size; i++, ptc++, pt++) { | ||||
| copy_v2_v2_int(mval, &ptc->x); | copy_v2_v2_int(mval, &ptc->x); | ||||
| if ((ED_view3d_autodist_depth(p->ar, mval, depth_margin, depth_arr + i) == 0) && | if ((ED_view3d_autodist_depth(p->ar, mval, depth_margin, depth_arr + i) == 0) && | ||||
| (i && (ED_view3d_autodist_depth_seg(p->ar, mval, mval_prev, depth_margin + 1, depth_arr + i) == 0))) | (i && (ED_view3d_autodist_depth_seg(p->ar, mval, mval_prev, depth_margin + 1, depth_arr + i) == 0))) | ||||
| { | { | ||||
| interp_depth = true; | interp_depth = true; | ||||
| } | } | ||||
| else { | else { | ||||
| found_depth = true; | found_depth = true; | ||||
| } | } | ||||
| copy_v2_v2_int(mval_prev, mval); | copy_v2_v2_int(mval_prev, mval); | ||||
| } | } | ||||
| if (found_depth == false) { | if (found_depth == false) { | ||||
| /* eeh... not much we can do.. :/, ignore depth in this case, use the 3D cursor */ | /* eeh... not much we can do.. :/, ignore depth in this case, use the 3D cursor */ | ||||
| for (i = gpd->sbuffer_size - 1; i >= 0; i--) | for (i = gpd->runtime.sbuffer_size - 1; i >= 0; i--) | ||||
| depth_arr[i] = 0.9999f; | depth_arr[i] = 0.9999f; | ||||
| } | } | ||||
| else { | else { | ||||
| if (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE_ENDPOINTS) { | if (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE_ENDPOINTS) { | ||||
| /* remove all info between the valid endpoints */ | /* remove all info between the valid endpoints */ | ||||
| int first_valid = 0; | int first_valid = 0; | ||||
| int last_valid = 0; | int last_valid = 0; | ||||
| for (i = 0; i < gpd->sbuffer_size; i++) { | for (i = 0; i < gpd->runtime.sbuffer_size; i++) { | ||||
| if (depth_arr[i] != FLT_MAX) | if (depth_arr[i] != FLT_MAX) | ||||
| break; | break; | ||||
| } | } | ||||
| first_valid = i; | first_valid = i; | ||||
| for (i = gpd->sbuffer_size - 1; i >= 0; i--) { | for (i = gpd->runtime.sbuffer_size - 1; i >= 0; i--) { | ||||
| if (depth_arr[i] != FLT_MAX) | if (depth_arr[i] != FLT_MAX) | ||||
| break; | break; | ||||
| } | } | ||||
| last_valid = i; | last_valid = i; | ||||
| /* invalidate non-endpoints, so only blend between first and last */ | /* invalidate non-endpoints, so only blend between first and last */ | ||||
| for (i = first_valid + 1; i < last_valid; i++) | for (i = first_valid + 1; i < last_valid; i++) | ||||
| depth_arr[i] = FLT_MAX; | depth_arr[i] = FLT_MAX; | ||||
| interp_depth = true; | interp_depth = true; | ||||
| } | } | ||||
| if (interp_depth) { | if (interp_depth) { | ||||
| interp_sparse_array(depth_arr, gpd->sbuffer_size, FLT_MAX); | interp_sparse_array(depth_arr, gpd->runtime.sbuffer_size, FLT_MAX); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| pt = gps->points; | pt = gps->points; | ||||
| /* convert all points (normal behavior) */ | /* convert all points (normal behavior) */ | ||||
| for (i = 0, ptc = gpd->sbuffer; i < gpd->sbuffer_size && ptc; i++, ptc++, pt++) { | for (i = 0, ptc = gpd->runtime.sbuffer; i < gpd->runtime.sbuffer_size && ptc; i++, ptc++, pt++) { | ||||
| /* convert screen-coordinates to appropriate coordinates (and store them) */ | /* convert screen-coordinates to appropriate coordinates (and store them) */ | ||||
| gp_stroke_convertcoords(p, &ptc->x, &pt->x, depth_arr ? depth_arr + i : NULL); | gp_stroke_convertcoords(p, &ptc->x, &pt->x, depth_arr ? depth_arr + i : NULL); | ||||
| /* copy pressure and time */ | /* copy pressure and time */ | ||||
| pt->pressure = ptc->pressure; | pt->pressure = ptc->pressure; | ||||
| pt->strength = ptc->strength; | pt->strength = ptc->strength; | ||||
| CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | ||||
| pt->time = ptc->time; | pt->time = ptc->time; | ||||
| } | } | ||||
| /* subdivide the stroke */ | |||||
| if (sublevel > 0) { | |||||
| int totpoints = gps->totpoints; | |||||
| for (i = 0; i < sublevel; i++) { | |||||
| /* we're adding one new point between each pair of verts on each step */ | |||||
| totpoints += totpoints - 1; | |||||
| gp_subdivide_stroke(gps, totpoints); | |||||
| } | |||||
| } | |||||
| /* apply randomness to stroke */ | |||||
| if (brush->draw_random_sub > 0.0f) { | |||||
| gp_randomize_stroke(gps, brush, p->rng); | |||||
| } | |||||
| /* smooth stroke after subdiv - only if there's something to do | |||||
| * for each iteration, the factor is reduced to get a better smoothing without changing too much | |||||
| * the original stroke | |||||
| */ | |||||
| if (brush->draw_smoothfac > 0.0f) { | |||||
| float reduce = 0.0f; | |||||
| for (int r = 0; r < brush->draw_smoothlvl; ++r) { | |||||
| for (i = 0; i < gps->totpoints; i++) { | |||||
| /* NOTE: No pressure smoothing, or else we get annoying thickness changes while drawing... */ | |||||
| gp_smooth_stroke(gps, i, brush->draw_smoothfac - reduce, false); | |||||
| } | |||||
| reduce += 0.25f; // reduce the factor | |||||
| } | |||||
| } | |||||
| /* if axis locked, reproject to plane locked (only in 3d space) */ | /* if axis locked, reproject to plane locked (only in 3d space) */ | ||||
| if (p->lock_axis > GP_LOCKAXIS_NONE) { | if (p->lock_axis > GP_LOCKAXIS_NONE) { | ||||
| gp_reproject_toplane(p, gps); | gp_reproject_toplane(p, gps); | ||||
| } | } | ||||
| /* if parented change position relative to parent object */ | |||||
| if (gpl->parent != NULL) { | |||||
| gp_apply_parent(gpl, gps); | |||||
| } | |||||
| if (depth_arr) | if (depth_arr) | ||||
| MEM_freeN(depth_arr); | MEM_freeN(depth_arr); | ||||
| } | } | ||||
| /* Save palette color */ | |||||
| bGPDpalette *palette = BKE_gpencil_palette_getactive(p->gpd); | /* add stroke to frame */ | ||||
| bGPDpalettecolor *palcolor = BKE_gpencil_palettecolor_getactive(palette); | |||||
| gps->palcolor = palcolor; | |||||
| BLI_strncpy(gps->colorname, palcolor->info, sizeof(gps->colorname)); | |||||
| /* 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 | |||||
| */ | |||||
| if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && (p->paintmode != GP_PAINTMODE_DRAW_POLY)) { | |||||
| BLI_addhead(&p->gpf->strokes, gps); | |||||
| } | |||||
| else { | |||||
| BLI_addtail(&p->gpf->strokes, gps); | BLI_addtail(&p->gpf->strokes, gps); | ||||
| } | |||||
| gp_stroke_added_enable(p); | gp_stroke_added_enable(p); | ||||
| } | } | ||||
| /* --- 'Eraser' for 'Paint' Tool ------ */ | /* --- 'Eraser' for 'Paint' Tool ------ */ | ||||
| /* helper to free a stroke | |||||
| * NOTE: gps->dvert and gps->triangles should be NULL, but check anyway for good measure | |||||
| */ | |||||
| static void gp_free_stroke(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps) | |||||
| { | |||||
| if (gps->points) { | |||||
| MEM_freeN(gps->points); | |||||
| } | |||||
| if (gps->dvert) { | |||||
| BKE_gpencil_free_stroke_weights(gps); | |||||
| MEM_freeN(gps->dvert); | |||||
| } | |||||
| if (gps->triangles) { | |||||
| MEM_freeN(gps->triangles); | |||||
| } | |||||
| BLI_freelinkN(&gpf->strokes, gps); | |||||
| } | |||||
| /* which which point is infront (result should only be used for comparison) */ | /* which which point is infront (result should only be used for comparison) */ | ||||
| static float view3d_point_depth(const RegionView3D *rv3d, const float co[3]) | static float view3d_point_depth(const RegionView3D *rv3d, const float co[3]) | ||||
| { | { | ||||
| if (rv3d->is_persp) { | if (rv3d->is_persp) { | ||||
| return ED_view3d_calc_zfac(rv3d, co, NULL); | return ED_view3d_calc_zfac(rv3d, co, NULL); | ||||
| } | } | ||||
| else { | else { | ||||
| return -dot_v3v3(rv3d->viewinv[2], co); | return -dot_v3v3(rv3d->viewinv[2], co); | ||||
| } | } | ||||
| } | } | ||||
| /* only erase stroke points that are visible */ | /* only erase stroke points that are visible (3d view) */ | ||||
| static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y) | static bool gp_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y) | ||||
| { | { | ||||
| if ((p->sa->spacetype == SPACE_VIEW3D) && | if ((p->sa->spacetype == SPACE_VIEW3D) && | ||||
| (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH)) | (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH)) | ||||
| { | { | ||||
| RegionView3D *rv3d = p->ar->regiondata; | RegionView3D *rv3d = p->ar->regiondata; | ||||
| bGPDlayer *gpl = p->gpl; | |||||
| const int mval[2] = {x, y}; | const int mval[2] = {x, y}; | ||||
| float mval_3d[3]; | float mval_3d[3]; | ||||
| float fpt[3]; | |||||
| float diff_mat[4][4]; | |||||
| /* calculate difference matrix if parent object */ | |||||
| ED_gpencil_parent_location(gpl, diff_mat); | |||||
| if (ED_view3d_autodist_simple(p->ar, mval, mval_3d, 0, NULL)) { | if (ED_view3d_autodist_simple(p->ar, mval, mval_3d, 0, NULL)) { | ||||
| const float depth_mval = view3d_point_depth(rv3d, mval_3d); | const float depth_mval = view3d_point_depth(rv3d, mval_3d); | ||||
| const float depth_pt = view3d_point_depth(rv3d, &pt->x); | |||||
| mul_v3_m4v3(fpt, diff_mat, &pt->x); | |||||
| const float depth_pt = view3d_point_depth(rv3d, fpt); | |||||
| if (depth_pt > depth_mval) { | if (depth_pt > depth_mval) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| /* apply a falloff effect to brush strength, based on distance */ | |||||
| static float gp_stroke_eraser_calc_influence(tGPsdata *p, const int mval[2], const int radius, const int co[2]) | |||||
| { | |||||
| /* Linear Falloff... */ | |||||
| float distance = (float)len_v2v2_int(mval, co); | |||||
| float fac; | |||||
| CLAMP(distance, 0.0f, (float)radius); | |||||
| fac = 1.0f - (distance / (float)radius); | |||||
| /* Control this further using pen pressure */ | |||||
| fac *= p->pressure; | |||||
| /* Return influence factor computed here */ | |||||
| return fac; | |||||
| } | |||||
| /* eraser tool - evaluation per stroke */ | /* eraser tool - evaluation per stroke */ | ||||
| /* TODO: this could really do with some optimization (KD-Tree/BVH?) */ | /* TODO: this could really do with some optimization (KD-Tree/BVH?) */ | ||||
| static void gp_stroke_eraser_dostroke(tGPsdata *p, | static void gp_stroke_eraser_dostroke(tGPsdata *p, | ||||
| bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, | bGPDframe *gpf, bGPDstroke *gps, | ||||
| const int mval[2], const int mvalo[2], | const int mval[2], const int mvalo[2], | ||||
| const int radius, const rcti *rect) | const int radius, const rcti *rect) | ||||
| { | { | ||||
| bGPDspoint *pt1, *pt2; | bGPDspoint *pt1, *pt2; | ||||
| int pc1[2] = {0}; | int pc1[2] = {0}; | ||||
| int pc2[2] = {0}; | int pc2[2] = {0}; | ||||
| int i; | int i; | ||||
| float diff_mat[4][4]; | |||||
| /* calculate difference matrix if parent object */ | |||||
| if (gpl->parent != NULL) { | |||||
| ED_gpencil_parent_location(gpl, diff_mat); | |||||
| } | |||||
| if (gps->totpoints == 0) { | if (gps->totpoints == 0) { | ||||
| /* just free stroke */ | /* just free stroke */ | ||||
| if (gps->points) | gp_free_stroke(p->gpd, gpf, gps); | ||||
| MEM_freeN(gps->points); | |||||
| if (gps->triangles) | |||||
| MEM_freeN(gps->triangles); | |||||
| BLI_freelinkN(&gpf->strokes, gps); | |||||
| } | } | ||||
| else if (gps->totpoints == 1) { | else if (gps->totpoints == 1) { | ||||
| /* only process if it hasn't been masked out... */ | /* only process if it hasn't been masked out... */ | ||||
| if (!(p->flags & GP_PAINTFLAG_SELECTMASK) || (gps->points->flag & GP_SPOINT_SELECT)) { | if (!(p->flags & GP_PAINTFLAG_SELECTMASK) || (gps->points->flag & GP_SPOINT_SELECT)) { | ||||
| if (gpl->parent == NULL) { | |||||
| gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]); | gp_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]); | ||||
| } | |||||
| else { | |||||
| bGPDspoint pt_temp; | |||||
| gp_point_to_parent_space(gps->points, diff_mat, &pt_temp); | |||||
| gp_point_to_xy(&p->gsc, gps, &pt_temp, &pc1[0], &pc1[1]); | |||||
| } | |||||
| /* do boundbox check first */ | /* do boundbox check first */ | ||||
| if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) { | if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) { | ||||
| /* only check if point is inside */ | /* only check if point is inside */ | ||||
| if (len_v2v2_int(mval, pc1) <= radius) { | if (len_v2v2_int(mval, pc1) <= radius) { | ||||
| /* free stroke */ | /* free stroke */ | ||||
| // XXX: pressure sensitive eraser should apply here too? | gp_free_stroke(p->gpd, gpf, gps); | ||||
| MEM_freeN(gps->points); | |||||
| if (gps->triangles) | |||||
| MEM_freeN(gps->triangles); | |||||
| BLI_freelinkN(&gpf->strokes, gps); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| /* Pressure threshold at which stroke should be culled: Calculated as pressure value | |||||
| * below which we would have invisible strokes | |||||
| */ | |||||
| const float cull_thresh = (gps->thickness) ? 1.0f / ((float)gps->thickness) : 1.0f; | |||||
| /* Amount to decrease the pressure of each point with each stroke */ | |||||
| // TODO: Fetch from toolsettings, or compute based on thickness instead? | |||||
| const float strength = 0.1f; | |||||
| /* Perform culling? */ | /* Perform culling? */ | ||||
| bool do_cull = false; | bool do_cull = false; | ||||
| /* Clear Tags | /* Clear Tags | ||||
| * | * | ||||
| * Note: It's better this way, as we are sure that | * Note: It's better this way, as we are sure that | ||||
| * we don't miss anything, though things will be | * we don't miss anything, though things will be | ||||
| * slightly slower as a result | * slightly slower as a result | ||||
| */ | */ | ||||
| for (i = 0; i < gps->totpoints; i++) { | for (i = 0; i < gps->totpoints; i++) { | ||||
| bGPDspoint *pt = &gps->points[i]; | bGPDspoint *pt = &gps->points[i]; | ||||
| pt->flag &= ~GP_SPOINT_TAG; | pt->flag &= ~GP_SPOINT_TAG; | ||||
| } | } | ||||
| /* First Pass: Loop over the points in the stroke | /* First Pass: Loop over the points in the stroke | ||||
| * 1) Thin out parts of the stroke under the brush | * 1) Thin out parts of the stroke under the brush | ||||
| * 2) Tag "too thin" parts for removal (in second pass) | * 2) Tag "too thin" parts for removal (in second pass) | ||||
| */ | */ | ||||
| for (i = 0; (i + 1) < gps->totpoints; i++) { | for (i = 0; (i + 1) < gps->totpoints; i++) { | ||||
| /* get points to work with */ | /* get points to work with */ | ||||
| pt1 = gps->points + i; | pt1 = gps->points + i; | ||||
| pt2 = gps->points + i + 1; | pt2 = gps->points + i + 1; | ||||
| /* only process if it hasn't been masked out... */ | /* only process if it hasn't been masked out... */ | ||||
| if ((p->flags & GP_PAINTFLAG_SELECTMASK) && !(gps->points->flag & GP_SPOINT_SELECT)) | if ((p->flags & GP_PAINTFLAG_SELECTMASK) && !(gps->points->flag & GP_SPOINT_SELECT)) | ||||
| continue; | continue; | ||||
| if (gpl->parent == NULL) { | |||||
| gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]); | gp_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]); | ||||
| gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]); | gp_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]); | ||||
| } | |||||
| else { | |||||
| bGPDspoint npt; | |||||
| gp_point_to_parent_space(pt1, diff_mat, &npt); | |||||
| gp_point_to_xy(&p->gsc, gps, &npt, &pc1[0], &pc1[1]); | |||||
| gp_point_to_parent_space(pt2, diff_mat, &npt); | |||||
| gp_point_to_xy(&p->gsc, gps, &npt, &pc2[0], &pc2[1]); | |||||
| } | |||||
| /* Check that point segment of the boundbox of the eraser stroke */ | /* Check that point segment of the boundbox of the eraser stroke */ | ||||
| if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) || | if (((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) || | ||||
| ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1]))) | ((!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1])) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1]))) | ||||
| { | { | ||||
| /* Check if point segment of stroke had anything to do with | /* Check if point segment of stroke had anything to do with | ||||
| * eraser region (either within stroke painted, or on its lines) | * eraser region (either within stroke painted, or on its lines) | ||||
| * - this assumes that linewidth is irrelevant | * - this assumes that linewidth is irrelevant | ||||
| */ | */ | ||||
| if (gp_stroke_inside_circle(mval, mvalo, radius, pc1[0], pc1[1], pc2[0], pc2[1])) { | if (gp_stroke_inside_circle(mval, mvalo, radius, pc1[0], pc1[1], pc2[0], pc2[1])) { | ||||
| if ((gp_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) || | if ((gp_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) || | ||||
| (gp_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false)) | (gp_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false)) | ||||
| { | { | ||||
| /* Point is affected: */ | /* Point is affected */ | ||||
| /* 1) Adjust thickness | |||||
| * - Influence of eraser falls off with distance from the middle of the eraser | |||||
| * - Second point gets less influence, as it might get hit again in the next segment | |||||
| */ | |||||
| pt1->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc1) * strength; | |||||
| pt2->pressure -= gp_stroke_eraser_calc_influence(p, mval, radius, pc2) * strength / 2.0f; | |||||
| /* 2) Tag any point with overly low influence for removal in the next pass */ | |||||
| if (pt1->pressure < cull_thresh) { | |||||
| pt1->flag |= GP_SPOINT_TAG; | pt1->flag |= GP_SPOINT_TAG; | ||||
| do_cull = true; | |||||
| } | |||||
| if (pt2->pressure < cull_thresh) { | |||||
| pt2->flag |= GP_SPOINT_TAG; | pt2->flag |= GP_SPOINT_TAG; | ||||
| do_cull = true; | do_cull = true; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| /* Second Pass: Remove any points that are tagged */ | /* Second Pass: Remove any points that are tagged */ | ||||
| if (do_cull) { | if (do_cull) { | ||||
| gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG); | gp_stroke_delete_tagged_points(gpf, gps, gps->next, GP_SPOINT_TAG, false); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* erase strokes which fall under the eraser strokes */ | /* erase strokes which fall under the eraser strokes */ | ||||
| static void gp_stroke_doeraser(tGPsdata *p) | static void gp_stroke_doeraser(tGPsdata *p) | ||||
| { | { | ||||
| bGPDlayer *gpl; | bGPDframe *gpf = p->gpf; | ||||
| bGPDstroke *gps, *gpn; | bGPDstroke *gps, *gpn; | ||||
| rcti rect; | rcti rect; | ||||
| /* rect is rectangle of eraser */ | /* rect is rectangle of eraser */ | ||||
| rect.xmin = p->mval[0] - p->radius; | rect.xmin = p->mval[0] - p->radius; | ||||
| rect.ymin = p->mval[1] - p->radius; | rect.ymin = p->mval[1] - p->radius; | ||||
| rect.xmax = p->mval[0] + p->radius; | rect.xmax = p->mval[0] + p->radius; | ||||
| rect.ymax = p->mval[1] + p->radius; | rect.ymax = p->mval[1] + p->radius; | ||||
| if (p->sa->spacetype == SPACE_VIEW3D) { | if (p->sa->spacetype == SPACE_VIEW3D) { | ||||
| if (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH) { | if (p->flags & GP_PAINTFLAG_V3D_ERASER_DEPTH) { | ||||
| View3D *v3d = p->sa->spacedata.first; | View3D *v3d = p->sa->spacedata.first; | ||||
| view3d_region_operator_needs_opengl(p->win, p->ar); | view3d_region_operator_needs_opengl(p->win, p->ar); | ||||
| ED_view3d_autodist_init(p->depsgraph, p->ar, v3d, 0); | ED_view3d_autodist_init(p->depsgraph, p->ar, v3d, 0); | ||||
| } | } | ||||
| } | } | ||||
| /* loop over all layers too, since while it's easy to restrict editing to | /* loop over strokes of active layer only (session init already took care of ensuring validity), | ||||
| * only a subset of layers, it is harder to perform the same erase operation | * checking segments for intersections to remove | ||||
| * on multiple layers... | |||||
| */ | */ | ||||
| for (gpl = p->gpd->layers.first; gpl; gpl = gpl->next) { | |||||
| bGPDframe *gpf = gpl->actframe; | |||||
| /* only affect layer if it's editable (and visible) */ | |||||
| if (gpencil_layer_is_editable(gpl) == false) { | |||||
| continue; | |||||
| } | |||||
| else if (gpf == NULL) { | |||||
| continue; | |||||
| } | |||||
| /* loop over strokes, checking segments for intersections */ | |||||
| for (gps = gpf->strokes.first; gps; gps = gpn) { | for (gps = gpf->strokes.first; gps; gps = gpn) { | ||||
| gpn = gps->next; | gpn = gps->next; | ||||
| /* check if the color is editable */ | |||||
| if (ED_gpencil_stroke_color_use(gpl, gps) == false) { | |||||
| continue; | |||||
| } | |||||
| /* Not all strokes in the datablock may be valid in the current editor/context | /* 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) | * (e.g. 2D space strokes in the 3D view, if the same datablock is shared) | ||||
| */ | */ | ||||
| if (ED_gpencil_stroke_can_use_direct(p->sa, gps)) { | if (ED_gpencil_stroke_can_use_direct(p->sa, gps)) { | ||||
| gp_stroke_eraser_dostroke(p, gpl, gpf, gps, p->mval, p->mvalo, p->radius, &rect); | gp_stroke_eraser_dostroke(p, gpf, gps, p->mval, p->mvalo, p->radius, &rect); | ||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* ******************************************* */ | /* ******************************************* */ | ||||
| /* Sketching Operator */ | /* Sketching Operator */ | ||||
| /* clear the session buffers (call this before AND after a paint operation) */ | /* clear the session buffers (call this before AND after a paint operation) */ | ||||
| static void gp_session_validatebuffer(tGPsdata *p) | static void gp_session_validatebuffer(tGPsdata *p) | ||||
| { | { | ||||
| bGPdata *gpd = p->gpd; | bGPdata *gpd = p->gpd; | ||||
| /* clear memory of buffer (or allocate it if starting a new session) */ | /* clear memory of buffer (or allocate it if starting a new session) */ | ||||
| if (gpd->sbuffer) { | if (gpd->runtime.sbuffer) { | ||||
| /* printf("\t\tGP - reset sbuffer\n"); */ | /* printf("\t\tGP - reset sbuffer\n"); */ | ||||
| memset(gpd->sbuffer, 0, sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX); | memset(gpd->runtime.sbuffer, 0, sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX); | ||||
| } | } | ||||
| else { | else { | ||||
| /* printf("\t\tGP - allocate sbuffer\n"); */ | /* printf("\t\tGP - allocate sbuffer\n"); */ | ||||
| gpd->sbuffer = MEM_callocN(sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer"); | gpd->runtime.sbuffer = MEM_callocN(sizeof(tGPspoint) * GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer"); | ||||
| } | } | ||||
| /* reset indices */ | /* reset indices */ | ||||
| gpd->sbuffer_size = 0; | gpd->runtime.sbuffer_size = 0; | ||||
| /* reset flags */ | /* reset flags */ | ||||
| gpd->sbuffer_sflag = 0; | gpd->runtime.sbuffer_sflag = 0; | ||||
| /* reset inittime */ | /* reset inittime */ | ||||
| p->inittime = 0.0; | p->inittime = 0.0; | ||||
| } | } | ||||
| /* create a new palette color */ | |||||
| static bGPDpalettecolor *gp_create_new_color(bGPDpalette *palette) | |||||
| { | |||||
| bGPDpalettecolor *palcolor; | |||||
| palcolor = BKE_gpencil_palettecolor_addnew(palette, DATA_("Color"), true); | |||||
| return palcolor; | |||||
| } | |||||
| /* initialize a drawing brush */ | |||||
| static void gp_init_drawing_brush(ToolSettings *ts, tGPsdata *p) | |||||
| { | |||||
| bGPDbrush *brush; | |||||
| /* if not exist, create a new one */ | |||||
| if (BLI_listbase_is_empty(&ts->gp_brushes)) { | |||||
| /* create new brushes */ | |||||
| BKE_gpencil_brush_init_presets(ts); | |||||
| brush = BKE_gpencil_brush_getactive(ts); | |||||
| } | |||||
| else { | |||||
| /* Use the current */ | |||||
| brush = BKE_gpencil_brush_getactive(ts); | |||||
| } | |||||
| /* be sure curves are initializated */ | |||||
| curvemapping_initialize(brush->cur_sensitivity); | |||||
| curvemapping_initialize(brush->cur_strength); | |||||
| curvemapping_initialize(brush->cur_jitter); | |||||
| /* asign to temp tGPsdata */ | |||||
| p->brush = brush; | |||||
| } | |||||
| /* initialize a paint palette brush and a default color if not exist */ | |||||
| static void gp_init_palette(tGPsdata *p) | |||||
| { | |||||
| bGPdata *gpd; | |||||
| bGPDpalette *palette; | |||||
| bGPDpalettecolor *palcolor; | |||||
| gpd = p->gpd; | |||||
| /* if not exist, create a new palette */ | |||||
| if (BLI_listbase_is_empty(&gpd->palettes)) { | |||||
| /* create new palette */ | |||||
| palette = BKE_gpencil_palette_addnew(gpd, DATA_("GP_Palette"), true); | |||||
| /* now create a default color */ | |||||
| palcolor = gp_create_new_color(palette); | |||||
| } | |||||
| else { | |||||
| /* Use the current palette and color */ | |||||
| palette = BKE_gpencil_palette_getactive(gpd); | |||||
| /* the palette needs one color */ | |||||
| if (BLI_listbase_is_empty(&palette->colors)) { | |||||
| palcolor = gp_create_new_color(palette); | |||||
| } | |||||
| else { | |||||
| palcolor = BKE_gpencil_palettecolor_getactive(palette); | |||||
| } | |||||
| /* in some situations can be null, so use first */ | |||||
| if (palcolor == NULL) { | |||||
| BKE_gpencil_palettecolor_setactive(palette, palette->colors.first); | |||||
| palcolor = palette->colors.first; | |||||
| } | |||||
| } | |||||
| /* asign to temp tGPsdata */ | |||||
| p->palettecolor = palcolor; | |||||
| } | |||||
| /* (re)init new painting data */ | /* (re)init new painting data */ | ||||
| static bool gp_session_initdata(bContext *C, tGPsdata *p) | static bool gp_session_initdata(bContext *C, tGPsdata *p) | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | Main *bmain = CTX_data_main(C); | ||||
| bGPdata **gpd_ptr = NULL; | bGPdata **gpd_ptr = NULL; | ||||
| ScrArea *curarea = CTX_wm_area(C); | ScrArea *curarea = CTX_wm_area(C); | ||||
| ARegion *ar = CTX_wm_region(C); | ARegion *ar = CTX_wm_region(C); | ||||
| ToolSettings *ts = CTX_data_tool_settings(C); | ToolSettings *ts = CTX_data_tool_settings(C); | ||||
| ▲ Show 20 Lines • Show All 122 Lines • ▼ Show 20 Lines | case SPACE_CLIP: | ||||
| copy_m4_m4(p->gsc.mat, p->mat); | copy_m4_m4(p->gsc.mat, p->mat); | ||||
| break; | break; | ||||
| } | } | ||||
| /* unsupported views */ | /* unsupported views */ | ||||
| default: | default: | ||||
| { | { | ||||
| p->status = GP_STATUS_ERROR; | p->status = GP_STATUS_ERROR; | ||||
| if (G.debug & G_DEBUG) | if (G.debug & G_DEBUG) | ||||
| printf("Error: Active view not appropriate for Grease Pencil drawing\n"); | printf("Error: Annotations are not supported in this editor\n"); | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| } | } | ||||
| /* get gp-data */ | /* get gp-data */ | ||||
| gpd_ptr = ED_gpencil_data_get_pointers(C, &p->ownerPtr); | gpd_ptr = ED_gpencil_data_get_pointers(C, &p->ownerPtr); | ||||
| if (gpd_ptr == NULL) { | if ((gpd_ptr == NULL) || !ED_gpencil_data_owner_is_annotation(&p->ownerPtr)) { | ||||
| p->status = GP_STATUS_ERROR; | p->status = GP_STATUS_ERROR; | ||||
| if (G.debug & G_DEBUG) | if (G.debug & G_DEBUG) | ||||
| printf("Error: Current context doesn't allow for any Grease Pencil data\n"); | printf("Error: Current context doesn't allow for any Annotation data\n"); | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| else { | else { | ||||
| /* if no existing GPencil block exists, add one */ | /* if no existing GPencil block exists, add one */ | ||||
| if (*gpd_ptr == NULL) | if (*gpd_ptr == NULL) { | ||||
| *gpd_ptr = BKE_gpencil_data_addnew(bmain, "GPencil"); | bGPdata *gpd = BKE_gpencil_data_addnew(bmain, "Annotations"); | ||||
| *gpd_ptr = gpd; | |||||
| /* mark datablock as being used for annotations */ | |||||
| gpd->flag |= GP_DATA_ANNOTATIONS; | |||||
| /* annotations always in front of all objects */ | |||||
| gpd->xray_mode = GP_XRAY_FRONT; | |||||
| } | |||||
| p->gpd = *gpd_ptr; | p->gpd = *gpd_ptr; | ||||
| } | } | ||||
| if (ED_gpencil_session_active() == 0) { | if (ED_gpencil_session_active() == 0) { | ||||
| /* initialize undo stack, | /* initialize undo stack, | ||||
| * also, existing undo stack would make buffer drawn | * also, existing undo stack would make buffer drawn | ||||
| */ | */ | ||||
| gpencil_undo_init(p->gpd); | gpencil_undo_init(p->gpd); | ||||
| } | } | ||||
| /* clear out buffer (stored in gp-data), in case something contaminated it */ | /* clear out buffer (stored in gp-data), in case something contaminated it */ | ||||
| gp_session_validatebuffer(p); | gp_session_validatebuffer(p); | ||||
| /* set brush and create a new one if null */ | |||||
| gp_init_drawing_brush(ts, p); | |||||
| /* set palette info and create a new one if null */ | |||||
| gp_init_palette(p); | |||||
| /* set palette colors */ | |||||
| bGPDpalettecolor *palcolor = p->palettecolor; | |||||
| bGPdata *pdata = p->gpd; | |||||
| copy_v4_v4(pdata->scolor, palcolor->color); | |||||
| copy_v4_v4(pdata->sfill, palcolor->fill); | |||||
| pdata->sflag = palcolor->flag; | |||||
| /* lock axis */ | /* lock axis */ | ||||
| p->lock_axis = ts->gp_sculpt.lock_axis; | p->lock_axis = ts->gp_sculpt.lock_axis; | ||||
| return 1; | return 1; | ||||
| } | } | ||||
| /* init new painting session */ | /* init new painting session */ | ||||
| static tGPsdata *gp_session_initpaint(bContext *C) | static tGPsdata *gp_session_initpaint(bContext *C) | ||||
| { | { | ||||
| tGPsdata *p = NULL; | tGPsdata *p = NULL; | ||||
| /* create new context data */ | /* create new context data */ | ||||
| p = MEM_callocN(sizeof(tGPsdata), "GPencil Drawing Data"); | p = MEM_callocN(sizeof(tGPsdata), "Annotation Drawing Data"); | ||||
| gp_session_initdata(C, p); | gp_session_initdata(C, p); | ||||
| /* radius for eraser circle is defined in userprefs now */ | /* radius for eraser circle is defined in userprefs now */ | ||||
| /* NOTE: we do this here, so that if we exit immediately, | /* NOTE: we do this here, so that if we exit immediately, | ||||
| * erase size won't get lost | * erase size won't get lost | ||||
| */ | */ | ||||
| p->radius = U.gp_eraser; | p->radius = U.gp_eraser; | ||||
| /* Random generator, only init once. */ | |||||
| uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX); | |||||
| rng_seed ^= GET_UINT_FROM_POINTER(p); | |||||
| p->rng = BLI_rng_new(rng_seed); | |||||
| /* return context data for running paint operator */ | /* return context data for running paint operator */ | ||||
| return p; | return p; | ||||
| } | } | ||||
| /* cleanup after a painting session */ | /* cleanup after a painting session */ | ||||
| static void gp_session_cleanup(tGPsdata *p) | static void gp_session_cleanup(tGPsdata *p) | ||||
| { | { | ||||
| bGPdata *gpd = (p) ? p->gpd : NULL; | bGPdata *gpd = (p) ? p->gpd : NULL; | ||||
| /* error checking */ | /* error checking */ | ||||
| if (gpd == NULL) | if (gpd == NULL) | ||||
| return; | return; | ||||
| /* free stroke buffer */ | /* free stroke buffer */ | ||||
| if (gpd->sbuffer) { | if (gpd->runtime.sbuffer) { | ||||
| /* printf("\t\tGP - free sbuffer\n"); */ | /* printf("\t\tGP - free sbuffer\n"); */ | ||||
| MEM_freeN(gpd->sbuffer); | MEM_freeN(gpd->runtime.sbuffer); | ||||
| gpd->sbuffer = NULL; | gpd->runtime.sbuffer = NULL; | ||||
| } | } | ||||
| /* clear flags */ | /* clear flags */ | ||||
| gpd->sbuffer_size = 0; | gpd->runtime.sbuffer_size = 0; | ||||
| gpd->sbuffer_sflag = 0; | gpd->runtime.sbuffer_sflag = 0; | ||||
| p->inittime = 0.0; | p->inittime = 0.0; | ||||
| } | } | ||||
| static void gp_session_free(tGPsdata *p) | static void gp_session_free(tGPsdata *p) | ||||
| { | { | ||||
| if (p->rng != NULL) { | |||||
| BLI_rng_free(p->rng); | |||||
| } | |||||
| MEM_freeN(p); | MEM_freeN(p); | ||||
| } | } | ||||
| /* init new stroke */ | /* init new stroke */ | ||||
| static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Depsgraph *depsgraph) | static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Depsgraph *depsgraph) | ||||
| { | { | ||||
| Scene *scene = p->scene; | Scene *scene = p->scene; | ||||
| ToolSettings *ts = scene->toolsettings; | ToolSettings *ts = scene->toolsettings; | ||||
| /* get active layer (or add a new one if non-existent) */ | /* get active layer (or add a new one if non-existent) */ | ||||
| p->gpl = BKE_gpencil_layer_getactive(p->gpd); | p->gpl = BKE_gpencil_layer_getactive(p->gpd); | ||||
| if (p->gpl == NULL) { | if (p->gpl == NULL) { | ||||
| p->gpl = BKE_gpencil_layer_addnew(p->gpd, "GP_Layer", true); | p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true); | ||||
| if (p->custom_color[3]) | if (p->custom_color[3]) | ||||
| copy_v3_v3(p->gpl->color, p->custom_color); | copy_v3_v3(p->gpl->color, p->custom_color); | ||||
| } | } | ||||
| if (p->gpl->flag & GP_LAYER_LOCKED) { | if (p->gpl->flag & GP_LAYER_LOCKED) { | ||||
| p->status = GP_STATUS_ERROR; | p->status = GP_STATUS_ERROR; | ||||
| if (G.debug & G_DEBUG) | if (G.debug & G_DEBUG) | ||||
| printf("Error: Cannot paint on locked layer\n"); | printf("Error: Cannot paint on locked layer\n"); | ||||
| return; | return; | ||||
| } | } | ||||
| /* get active frame (add a new one if not matching frame) */ | /* get active frame (add a new one if not matching frame) */ | ||||
| if (paintmode == GP_PAINTMODE_ERASER) { | if (paintmode == GP_PAINTMODE_ERASER) { | ||||
| /* Eraser mode: | /* Eraser mode: | ||||
| * 1) Add new frames to all frames that we might touch, | * 1) Only allow erasing on the active layer (unlike for 3d-art Grease Pencil), | ||||
| * since we won't be exposing layer locking in the UI | |||||
| * 2) Ensure that p->gpf refers to the frame used for the active layer | * 2) Ensure that p->gpf refers to the frame used for the active layer | ||||
| * (to avoid problems with other tools which expect it to exist) | * (to avoid problems with other tools which expect it to exist) | ||||
| */ | */ | ||||
| bool has_layer_to_erase = false; | bool has_layer_to_erase = false; | ||||
| for (bGPDlayer *gpl = p->gpd->layers.first; gpl; gpl = gpl->next) { | if (gpencil_layer_is_editable(p->gpl)) { | ||||
| /* Skip if layer not editable */ | /* Ensure that there's stuff to erase here (not including selection mask below)... */ | ||||
| if (gpencil_layer_is_editable(gpl) == false) | if (p->gpl->actframe && p->gpl->actframe->strokes.first) { | ||||
| continue; | |||||
| /* Add a new frame if needed (and based off the active frame, | |||||
| * as we need some existing strokes to erase) | |||||
| * | |||||
| * Note: We don't add a new frame if there's nothing there now, so | |||||
| * -> If there are no frames at all, don't add one | |||||
| * -> If there are no strokes in that frame, don't add a new empty frame | |||||
| */ | |||||
| if (gpl->actframe && gpl->actframe->strokes.first) { | |||||
| gpl->actframe = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_COPY); | |||||
| has_layer_to_erase = true; | has_layer_to_erase = true; | ||||
| } | } | ||||
| /* XXX: we omit GP_FRAME_PAINT here for now, | |||||
| * as it is only really useful for doing | |||||
| * paintbuffer drawing | |||||
| */ | |||||
| } | } | ||||
| /* Ensure this gets set... */ | /* Ensure active frame is set correctly... */ | ||||
| p->gpf = p->gpl->actframe; | p->gpf = p->gpl->actframe; | ||||
| /* Restrict eraser to only affecting selected strokes, if the "selection mask" is on | /* Restrict eraser to only affecting selected strokes, if the "selection mask" is on | ||||
| * (though this is only available in editmode) | * (though this is only available in editmode) | ||||
| */ | */ | ||||
| if (p->gpd->flag & GP_DATA_STROKE_EDITMODE) { | if (p->gpd->flag & GP_DATA_STROKE_EDITMODE) { | ||||
| if (ts->gp_sculpt.flag & GP_BRUSHEDIT_FLAG_SELECT_MASK) { | if (ts->gp_sculpt.flag & GP_BRUSHEDIT_FLAG_SELECT_MASK) { | ||||
| p->flags |= GP_PAINTFLAG_SELECTMASK; | p->flags |= GP_PAINTFLAG_SELECTMASK; | ||||
| } | } | ||||
| } | } | ||||
| if (has_layer_to_erase == false) { | if (has_layer_to_erase == false) { | ||||
| p->status = GP_STATUS_ERROR; | p->status = GP_STATUS_ERROR; | ||||
| //if (G.debug & G_DEBUG) | //if (G.debug & G_DEBUG) | ||||
| printf("Error: Eraser will not be affecting anything (gpencil_paint_init)\n"); | printf("Error: Eraser will not be affecting anything (gpencil_paint_init)\n"); | ||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| /* Drawing Modes - Add a new frame if needed on the active layer */ | /* Drawing Modes - Add a new frame if needed on the active layer */ | ||||
| short add_frame_mode; | short add_frame_mode = GP_GETFRAME_ADD_NEW; | ||||
| if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) | if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) | ||||
| add_frame_mode = GP_GETFRAME_ADD_COPY; | add_frame_mode = GP_GETFRAME_ADD_COPY; | ||||
| else | else | ||||
| add_frame_mode = GP_GETFRAME_ADD_NEW; | add_frame_mode = GP_GETFRAME_ADD_NEW; | ||||
| p->gpf = BKE_gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode); | p->gpf = BKE_gpencil_layer_getframe(p->gpl, CFRA, add_frame_mode); | ||||
| if (p->gpf == NULL) { | if (p->gpf == NULL) { | ||||
| p->status = GP_STATUS_ERROR; | p->status = GP_STATUS_ERROR; | ||||
| if (G.debug & G_DEBUG) | if (G.debug & G_DEBUG) | ||||
| printf("Error: No frame created (gpencil_paint_init)\n"); | printf("Error: No frame created (gpencil_paint_init)\n"); | ||||
| return; | return; | ||||
| } | } | ||||
| else { | else { | ||||
| p->gpf->flag |= GP_FRAME_PAINT; | p->gpf->flag |= GP_FRAME_PAINT; | ||||
| } | } | ||||
| } | } | ||||
| /* set 'eraser' for this stroke if using eraser */ | /* set 'eraser' for this stroke if using eraser */ | ||||
| p->paintmode = paintmode; | p->paintmode = paintmode; | ||||
| if (p->paintmode == GP_PAINTMODE_ERASER) { | if (p->paintmode == GP_PAINTMODE_ERASER) { | ||||
| p->gpd->sbuffer_sflag |= GP_STROKE_ERASER; | p->gpd->runtime.sbuffer_sflag |= GP_STROKE_ERASER; | ||||
| /* check if we should respect depth while erasing */ | /* check if we should respect depth while erasing */ | ||||
| if (p->sa->spacetype == SPACE_VIEW3D) { | if (p->sa->spacetype == SPACE_VIEW3D) { | ||||
| if (p->gpl->flag & GP_LAYER_NO_XRAY) { | if (p->gpl->flag & GP_LAYER_NO_XRAY) { | ||||
| p->flags |= GP_PAINTFLAG_V3D_ERASER_DEPTH; | p->flags |= GP_PAINTFLAG_V3D_ERASER_DEPTH; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| /* disable eraser flags - so that we can switch modes during a session */ | /* disable eraser flags - so that we can switch modes during a session */ | ||||
| p->gpd->sbuffer_sflag &= ~GP_STROKE_ERASER; | p->gpd->runtime.sbuffer_sflag &= ~GP_STROKE_ERASER; | ||||
| if (p->sa->spacetype == SPACE_VIEW3D) { | if (p->sa->spacetype == SPACE_VIEW3D) { | ||||
| if (p->gpl->flag & GP_LAYER_NO_XRAY) { | if (p->gpl->flag & GP_LAYER_NO_XRAY) { | ||||
| p->flags &= ~GP_PAINTFLAG_V3D_ERASER_DEPTH; | p->flags &= ~GP_PAINTFLAG_V3D_ERASER_DEPTH; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Show All 30 Lines | static void gp_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Depsgraph *depsgraph) | ||||
| copy_m4_m4(p->gsc.mat, p->mat); | copy_m4_m4(p->gsc.mat, p->mat); | ||||
| /* check if points will need to be made in view-aligned space */ | /* check if points will need to be made in view-aligned space */ | ||||
| if (*p->align_flag & GP_PROJECT_VIEWSPACE) { | if (*p->align_flag & GP_PROJECT_VIEWSPACE) { | ||||
| switch (p->sa->spacetype) { | switch (p->sa->spacetype) { | ||||
| case SPACE_VIEW3D: | case SPACE_VIEW3D: | ||||
| { | { | ||||
| p->gpd->sbuffer_sflag |= GP_STROKE_3DSPACE; | p->gpd->runtime.sbuffer_sflag |= GP_STROKE_3DSPACE; | ||||
| break; | break; | ||||
| } | } | ||||
| case SPACE_NODE: | case SPACE_NODE: | ||||
| { | { | ||||
| p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; | p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE; | ||||
| break; | break; | ||||
| } | } | ||||
| case SPACE_SEQ: | case SPACE_SEQ: | ||||
| { | { | ||||
| p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; | p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE; | ||||
| break; | break; | ||||
| } | } | ||||
| case SPACE_IMAGE: | case SPACE_IMAGE: | ||||
| { | { | ||||
| SpaceImage *sima = (SpaceImage *)p->sa->spacedata.first; | SpaceImage *sima = (SpaceImage *)p->sa->spacedata.first; | ||||
| /* only set these flags if the image editor doesn't have an image active, | /* only set these flags if the image editor doesn't have an image active, | ||||
| * otherwise user will be confused by strokes not appearing after they're drawn | * otherwise user will be confused by strokes not appearing after they're drawn | ||||
| * | * | ||||
| * Admittedly, this is a bit hacky, but it works much nicer from an ergonomic standpoint! | * Admittedly, this is a bit hacky, but it works much nicer from an ergonomic standpoint! | ||||
| */ | */ | ||||
| if (ELEM(NULL, sima, sima->image)) { | if (ELEM(NULL, sima, sima->image)) { | ||||
| /* make strokes be drawn in screen space */ | /* make strokes be drawn in screen space */ | ||||
| p->gpd->sbuffer_sflag &= ~GP_STROKE_2DSPACE; | p->gpd->runtime.sbuffer_sflag &= ~GP_STROKE_2DSPACE; | ||||
| *(p->align_flag) &= ~GP_PROJECT_VIEWSPACE; | *(p->align_flag) &= ~GP_PROJECT_VIEWSPACE; | ||||
| } | } | ||||
| else { | else { | ||||
| p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; | p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE; | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| case SPACE_CLIP: | case SPACE_CLIP: | ||||
| { | { | ||||
| p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; | p->gpd->runtime.sbuffer_sflag |= GP_STROKE_2DSPACE; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */ | /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */ | ||||
| static void gp_paint_strokeend(tGPsdata *p) | static void gp_paint_strokeend(tGPsdata *p) | ||||
| { | { | ||||
| ToolSettings *ts = p->scene->toolsettings; | ToolSettings *ts = p->scene->toolsettings; | ||||
| /* for surface sketching, need to set the right OpenGL context stuff so that | /* for surface sketching, need to set the right OpenGL context stuff so that | ||||
| * the conversions will project the values correctly... | * the conversions will project the values correctly... | ||||
| */ | */ | ||||
| if (gpencil_project_check(p)) { | if (gpencil_project_check(p)) { | ||||
| View3D *v3d = p->sa->spacedata.first; | View3D *v3d = p->sa->spacedata.first; | ||||
| /* need to restore the original projection settings before packing up */ | /* need to restore the original projection settings before packing up */ | ||||
| view3d_region_operator_needs_opengl(p->win, p->ar); | view3d_region_operator_needs_opengl(p->win, p->ar); | ||||
| ED_view3d_autodist_init(p->depsgraph, p->ar, v3d, (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 1 : 0); | ED_view3d_autodist_init(p->depsgraph, p->ar, v3d, (ts->gpencil_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 1 : 0); | ||||
| } | } | ||||
| /* check if doing eraser or not */ | /* check if doing eraser or not */ | ||||
| if ((p->gpd->sbuffer_sflag & GP_STROKE_ERASER) == 0) { | if ((p->gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) { | ||||
| /* simplify stroke before transferring? */ | /* simplify stroke before transferring? */ | ||||
| gp_stroke_simplify(p); | gp_stroke_simplify(p); | ||||
| /* transfer stroke to frame */ | /* transfer stroke to frame */ | ||||
| gp_stroke_newfrombuffer(p); | gp_stroke_newfrombuffer(p); | ||||
| } | } | ||||
| /* clean up buffer now */ | /* clean up buffer now */ | ||||
| ▲ Show 20 Lines • Show All 175 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| /* update UI indicators of status, including cursor and header prints */ | /* update UI indicators of status, including cursor and header prints */ | ||||
| static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) | static void gpencil_draw_status_indicators(bContext *C, tGPsdata *p) | ||||
| { | { | ||||
| /* header prints */ | /* header prints */ | ||||
| switch (p->status) { | switch (p->status) { | ||||
| case GP_STATUS_PAINTING: | case GP_STATUS_PAINTING: | ||||
| /* only print this for paint-sessions, otherwise it gets annoying */ | switch (p->paintmode) { | ||||
| if (GPENCIL_SKETCH_SESSIONS_ON(p->scene)) | case GP_PAINTMODE_DRAW_POLY: | ||||
| ED_workspace_status_text(C, IFACE_("Grease Pencil: Drawing/erasing stroke... Release to end stroke")); | /* Provide usage tips, since this is modal, and unintuitive without hints */ | ||||
| ED_workspace_status_text(C, IFACE_("Annotation Create Poly: LMB click to place next stroke vertex | " | |||||
| "ESC/Enter to end (or click outside this area)")); | |||||
| break; | |||||
| default: | |||||
| /* Do nothing - the others are self explanatory, exit quickly once the mouse is released | |||||
| * Showing any text would just be annoying as it would flicker. | |||||
| */ | |||||
| break; | |||||
| } | |||||
| break; | break; | ||||
| case GP_STATUS_IDLING: | case GP_STATUS_IDLING: | ||||
| /* print status info */ | /* print status info */ | ||||
| switch (p->paintmode) { | switch (p->paintmode) { | ||||
| case GP_PAINTMODE_ERASER: | case GP_PAINTMODE_ERASER: | ||||
| ED_workspace_status_text(C, IFACE_("Grease Pencil Erase Session: Hold and drag LMB or RMB to erase | " | ED_workspace_status_text(C, IFACE_("Annotation Eraser: Hold and drag LMB or RMB to erase | " | ||||
| "ESC/Enter to end (or click outside this area)")); | "ESC/Enter to end (or click outside this area)")); | ||||
| break; | break; | ||||
| case GP_PAINTMODE_DRAW_STRAIGHT: | case GP_PAINTMODE_DRAW_STRAIGHT: | ||||
| ED_workspace_status_text(C, IFACE_("Grease Pencil Line Session: Hold and drag LMB to draw | " | ED_workspace_status_text(C, IFACE_("Annotation Line Draw: Hold and drag LMB to draw | " | ||||
| "ESC/Enter to end (or click outside this area)")); | "ESC/Enter to end (or click outside this area)")); | ||||
| break; | break; | ||||
| case GP_PAINTMODE_DRAW: | case GP_PAINTMODE_DRAW: | ||||
| ED_workspace_status_text(C, IFACE_("Grease Pencil Freehand Session: Hold and drag LMB to draw | " | ED_workspace_status_text(C, IFACE_("Annotation Freehand Draw: Hold and drag LMB to draw | " | ||||
| "E/ESC/Enter to end (or click outside this area)")); | "E/ESC/Enter to end (or click outside this area)")); | ||||
| break; | break; | ||||
| case GP_PAINTMODE_DRAW_POLY: | case GP_PAINTMODE_DRAW_POLY: | ||||
| ED_workspace_status_text(C, IFACE_("Grease Pencil Poly Session: LMB click to place next stroke vertex | " | ED_workspace_status_text(C, IFACE_("Annotation Create Poly: LMB click to place next stroke vertex | " | ||||
| "ESC/Enter to end (or click outside this area)")); | "ESC/Enter to end (or click outside this area)")); | ||||
| break; | break; | ||||
| default: /* unhandled future cases */ | default: /* unhandled future cases */ | ||||
| ED_workspace_status_text(C, IFACE_("Grease Pencil Session: ESC/Enter to end (or click outside this area)")); | ED_workspace_status_text(C, IFACE_("Annotation Session: ESC/Enter to end (or click outside this area)")); | ||||
| break; | break; | ||||
| } | } | ||||
| break; | break; | ||||
| case GP_STATUS_ERROR: | case GP_STATUS_ERROR: | ||||
| case GP_STATUS_DONE: | case GP_STATUS_DONE: | ||||
| /* clear status string */ | /* clear status string */ | ||||
| ED_workspace_status_text(C, NULL); | ED_workspace_status_text(C, NULL); | ||||
| ▲ Show 20 Lines • Show All 378 Lines • ▼ Show 20 Lines | static void gpencil_move_last_stroke_to_back(bContext *C) | ||||
| BLI_remlink(&gpf->strokes, gps); | BLI_remlink(&gpf->strokes, gps); | ||||
| BLI_insertlinkbefore(&gpf->strokes, gpf->strokes.first, gps); | BLI_insertlinkbefore(&gpf->strokes, gpf->strokes.first, gps); | ||||
| } | } | ||||
| /* events handling during interactive drawing part of operator */ | /* events handling during interactive drawing part of operator */ | ||||
| static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) | static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) | ||||
| { | { | ||||
| tGPsdata *p = op->customdata; | tGPsdata *p = op->customdata; | ||||
| ToolSettings *ts = CTX_data_tool_settings(C); | |||||
| int estate = OPERATOR_PASS_THROUGH; /* default exit state - pass through to support MMB view nav, etc. */ | int estate = OPERATOR_PASS_THROUGH; /* default exit state - pass through to support MMB view nav, etc. */ | ||||
| /* if (event->type == NDOF_MOTION) | /* if (event->type == NDOF_MOTION) | ||||
| * return OPERATOR_PASS_THROUGH; | * return OPERATOR_PASS_THROUGH; | ||||
| * ------------------------------- | * ------------------------------- | ||||
| * [mce] Not quite what I was looking | * [mce] Not quite what I was looking | ||||
| * for, but a good start! GP continues to | * for, but a good start! GP continues to | ||||
| * draw on the screen while the 3D mouse | * draw on the screen while the 3D mouse | ||||
| Show All 19 Lines | if (ISKEYBOARD(event->type)) { | ||||
| } | } | ||||
| else if (ELEM(event->type, PAD0, PAD1, PAD2, PAD3, PAD4, PAD5, PAD6, PAD7, PAD8, PAD9)) { | else if (ELEM(event->type, PAD0, PAD1, PAD2, PAD3, PAD4, PAD5, PAD6, PAD7, PAD8, PAD9)) { | ||||
| /* allow numpad keys so that camera/view manipulations can still take place | /* allow numpad keys so that camera/view manipulations can still take place | ||||
| * - PAD0 in particular is really important for Grease Pencil drawing, | * - PAD0 in particular is really important for Grease Pencil drawing, | ||||
| * as animators may be working "to camera", so having this working | * as animators may be working "to camera", so having this working | ||||
| * is essential for ensuring that they can quickly return to that view | * is essential for ensuring that they can quickly return to that view | ||||
| */ | */ | ||||
| } | } | ||||
| else if ((ELEM(event->type, p->keymodifier)) && (event->val == KM_RELEASE)) { | |||||
| /* enable continuous if release D key in mid drawing */ | |||||
| p->scene->toolsettings->gpencil_flags |= GP_TOOL_FLAG_PAINTSESSIONS_ON; | |||||
| } | |||||
| else if ((event->type == BKEY) && (event->val == KM_RELEASE)) { | else if ((event->type == BKEY) && (event->val == KM_RELEASE)) { | ||||
| /* Add Blank Frame | /* Add Blank Frame | ||||
| * - Since this operator is non-modal, we can just call it here, and keep going... | * - Since this operator is non-modal, we can just call it here, and keep going... | ||||
| * - This operator is especially useful when animating | * - This operator is especially useful when animating | ||||
| */ | */ | ||||
| WM_operator_name_call(C, "GPENCIL_OT_blank_frame_add", WM_OP_EXEC_DEFAULT, NULL); | WM_operator_name_call(C, "GPENCIL_OT_blank_frame_add", WM_OP_EXEC_DEFAULT, NULL); | ||||
| estate = OPERATOR_RUNNING_MODAL; | estate = OPERATOR_RUNNING_MODAL; | ||||
| } | } | ||||
| else { | else { | ||||
| estate = OPERATOR_RUNNING_MODAL; | estate = OPERATOR_RUNNING_MODAL; | ||||
| } | } | ||||
| } | } | ||||
| //printf("\tGP - handle modal event...\n"); | //printf("\tGP - handle modal event...\n"); | ||||
| /* exit painting mode (and/or end current stroke) | /* exit painting mode (and/or end current stroke) | ||||
| * NOTE: cannot do RIGHTMOUSE (as is standard for canceling) as that would break polyline [#32647] | * NOTE: cannot do RIGHTMOUSE (as is standard for canceling) as that would break polyline [#32647] | ||||
| */ | */ | ||||
| if (ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) { | if (ELEM(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY, EKEY)) { | ||||
| /* exit() ends the current stroke before cleaning up */ | /* exit() ends the current stroke before cleaning up */ | ||||
| /* printf("\t\tGP - end of paint op + end of stroke\n"); */ | /* printf("\t\tGP - end of paint op + end of stroke\n"); */ | ||||
| /* if drawing polygon and enable on back, must move stroke */ | |||||
| if (ts) { | |||||
| if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { | |||||
| if (p->flags & GP_PAINTFLAG_STROKEADDED) { | |||||
| gpencil_move_last_stroke_to_back(C); | |||||
| } | |||||
| } | |||||
| } | |||||
| p->status = GP_STATUS_DONE; | p->status = GP_STATUS_DONE; | ||||
| estate = OPERATOR_FINISHED; | estate = OPERATOR_FINISHED; | ||||
| } | } | ||||
| /* toggle painting mode upon mouse-button movement | /* toggle painting mode upon mouse-button movement | ||||
| * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) / polyline (toolbox only) | * - LEFTMOUSE = standard drawing (all) / straight line drawing (all) / polyline (toolbox only) | ||||
| * - RIGHTMOUSE = polyline (hotkey) / eraser (all) | * - RIGHTMOUSE = polyline (hotkey) / eraser (all) | ||||
| * (Disabling RIGHTMOUSE case here results in bugs like [#32647]) | * (Disabling RIGHTMOUSE case here results in bugs like [#32647]) | ||||
| * also making sure we have a valid event value, to not exit too early | * also making sure we have a valid event value, to not exit too early | ||||
| */ | */ | ||||
| if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE) && (ELEM(event->val, KM_PRESS, KM_RELEASE))) { | if (ELEM(event->type, LEFTMOUSE, RIGHTMOUSE) && (ELEM(event->val, KM_PRESS, KM_RELEASE))) { | ||||
| /* if painting, end stroke */ | /* if painting, end stroke */ | ||||
| if (p->status == GP_STATUS_PAINTING) { | if (p->status == GP_STATUS_PAINTING) { | ||||
| int sketch = 0; | int sketch = 0; | ||||
| /* basically, this should be mouse-button up = end stroke | /* basically, this should be mouse-button up = end stroke | ||||
| * BUT what happens next depends on whether we 'painting sessions' is enabled | * BUT, polyline drawing is an exception -- all knots should be added during one session | ||||
| */ | */ | ||||
| sketch |= GPENCIL_SKETCH_SESSIONS_ON(p->scene); | |||||
| /* polyline drawing is also 'sketching' -- all knots should be added during one session */ | |||||
| sketch |= (p->paintmode == GP_PAINTMODE_DRAW_POLY); | sketch |= (p->paintmode == GP_PAINTMODE_DRAW_POLY); | ||||
| if (sketch) { | if (sketch) { | ||||
| /* end stroke only, and then wait to resume painting soon */ | /* end stroke only, and then wait to resume painting soon */ | ||||
| /* printf("\t\tGP - end stroke only\n"); */ | /* printf("\t\tGP - end stroke only\n"); */ | ||||
| gpencil_stroke_end(op); | gpencil_stroke_end(op); | ||||
| /* If eraser mode is on, turn it off after the stroke finishes | /* If eraser mode is on, turn it off after the stroke finishes | ||||
| Show All 20 Lines | if (p->status == GP_STATUS_PAINTING) { | ||||
| /* we've just entered idling state, so this event was processed (but no others yet) */ | /* we've just entered idling state, so this event was processed (but no others yet) */ | ||||
| estate = OPERATOR_RUNNING_MODAL; | estate = OPERATOR_RUNNING_MODAL; | ||||
| /* stroke could be smoothed, send notifier to refresh screen */ | /* stroke could be smoothed, send notifier to refresh screen */ | ||||
| WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); | WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL); | ||||
| } | } | ||||
| else { | else { | ||||
| /* printf("\t\tGP - end of stroke + op\n"); */ | /* printf("\t\tGP - end of stroke + op\n"); */ | ||||
| /* if drawing polygon and enable on back, must move stroke */ | |||||
| if (ts) { | |||||
| if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { | |||||
| if (p->flags & GP_PAINTFLAG_STROKEADDED) { | |||||
| gpencil_move_last_stroke_to_back(C); | |||||
| } | |||||
| } | |||||
| } | |||||
| p->status = GP_STATUS_DONE; | p->status = GP_STATUS_DONE; | ||||
| estate = OPERATOR_FINISHED; | estate = OPERATOR_FINISHED; | ||||
| } | } | ||||
| } | } | ||||
| else if (event->val == KM_PRESS) { | else if (event->val == KM_PRESS) { | ||||
| bool in_bounds = false; | bool in_bounds = false; | ||||
| /* Check if we're outside the bounds of the active region | /* Check if we're outside the bounds of the active region | ||||
| ▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | else if (event->val == KM_PRESS) { | ||||
| estate = OPERATOR_CANCELLED; | estate = OPERATOR_CANCELLED; | ||||
| } | } | ||||
| } | } | ||||
| else if (p->status != GP_STATUS_ERROR) { | else if (p->status != GP_STATUS_ERROR) { | ||||
| /* User clicked outside bounds of window while idling, so exit paintmode | /* User clicked outside bounds of window while idling, so exit paintmode | ||||
| * NOTE: Don't enter this case if an error occurred while finding the | * NOTE: Don't enter this case if an error occurred while finding the | ||||
| * region (as above) | * region (as above) | ||||
| */ | */ | ||||
| /* if drawing polygon and enable on back, must move stroke */ | |||||
| if (ts) { | |||||
| if ((ts->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) && (p->paintmode == GP_PAINTMODE_DRAW_POLY)) { | |||||
| if (p->flags & GP_PAINTFLAG_STROKEADDED) { | |||||
| gpencil_move_last_stroke_to_back(C); | |||||
| } | |||||
| } | |||||
| } | |||||
| p->status = GP_STATUS_DONE; | p->status = GP_STATUS_DONE; | ||||
| estate = OPERATOR_FINISHED; | estate = OPERATOR_FINISHED; | ||||
| } | } | ||||
| } | } | ||||
| else if (event->val == KM_RELEASE) { | else if (event->val == KM_RELEASE) { | ||||
| p->status = GP_STATUS_IDLING; | p->status = GP_STATUS_IDLING; | ||||
| op->flag |= OP_IS_MODAL_CURSOR_REGION; | op->flag |= OP_IS_MODAL_CURSOR_REGION; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| /* ------------------------------- */ | /* ------------------------------- */ | ||||
| static const EnumPropertyItem prop_gpencil_drawmodes[] = { | static const EnumPropertyItem prop_gpencil_drawmodes[] = { | ||||
| {GP_PAINTMODE_DRAW, "DRAW", 0, "Draw Freehand", "Draw freehand stroke(s)"}, | {GP_PAINTMODE_DRAW, "DRAW", 0, "Draw Freehand", "Draw freehand stroke(s)"}, | ||||
| {GP_PAINTMODE_DRAW_STRAIGHT, "DRAW_STRAIGHT", 0, "Draw Straight Lines", "Draw straight line segment(s)"}, | {GP_PAINTMODE_DRAW_STRAIGHT, "DRAW_STRAIGHT", 0, "Draw Straight Lines", "Draw straight line segment(s)"}, | ||||
| {GP_PAINTMODE_DRAW_POLY, "DRAW_POLY", 0, "Draw Poly Line", "Click to place endpoints of straight line segments (connected)"}, | {GP_PAINTMODE_DRAW_POLY, "DRAW_POLY", 0, "Draw Poly Line", "Click to place endpoints of straight line segments (connected)"}, | ||||
| {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", "Erase Grease Pencil strokes"}, | {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", "Erase Annotation strokes"}, | ||||
| {0, NULL, 0, NULL, NULL} | {0, NULL, 0, NULL, NULL} | ||||
| }; | }; | ||||
| void GPENCIL_OT_draw(wmOperatorType *ot) | void GPENCIL_OT_annotate(wmOperatorType *ot) | ||||
| { | { | ||||
| PropertyRNA *prop; | PropertyRNA *prop; | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Grease Pencil Draw"; | ot->name = "Annotation Draw"; | ||||
| ot->idname = "GPENCIL_OT_draw"; | ot->idname = "GPENCIL_OT_annotate"; | ||||
| ot->description = "Make annotations on the active data"; | ot->description = "Make annotations on the active data"; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->exec = gpencil_draw_exec; | ot->exec = gpencil_draw_exec; | ||||
| ot->invoke = gpencil_draw_invoke; | ot->invoke = gpencil_draw_invoke; | ||||
| ot->modal = gpencil_draw_modal; | ot->modal = gpencil_draw_modal; | ||||
| ot->cancel = gpencil_draw_cancel; | ot->cancel = gpencil_draw_cancel; | ||||
| ot->poll = gpencil_draw_poll; | ot->poll = gpencil_draw_poll; | ||||
| Show All 14 Lines | |||||