diff options
Diffstat (limited to 'source/blender/editors')
-rw-r--r-- | source/blender/editors/Makefile | 2 | ||||
-rw-r--r-- | source/blender/editors/SConscript | 2 | ||||
-rw-r--r-- | source/blender/editors/gpencil/drawgpencil.c | 1061 | ||||
-rw-r--r-- | source/blender/editors/gpencil/editaction_gpencil.c | 739 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil.c | 2162 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_intern.h | 81 | ||||
-rw-r--r-- | source/blender/editors/include/ED_armature.h | 3 | ||||
-rw-r--r-- | source/blender/editors/include/ED_gpencil.h | 101 | ||||
-rw-r--r-- | source/blender/editors/include/ED_sequencer.h | 31 | ||||
-rw-r--r-- | source/blender/editors/include/ED_view3d.h | 2 | ||||
-rw-r--r-- | source/blender/editors/sculpt/Makefile | 57 | ||||
-rw-r--r-- | source/blender/editors/space_sequencer/sequencer_draw.c | 1 | ||||
-rw-r--r-- | source/blender/editors/space_sequencer/sequencer_intern.h | 2 | ||||
-rw-r--r-- | source/blender/editors/space_view3d/view3d_header.c | 6 |
14 files changed, 4243 insertions, 7 deletions
diff --git a/source/blender/editors/Makefile b/source/blender/editors/Makefile index ed59a82d2b2..b7b2096fea9 100644 --- a/source/blender/editors/Makefile +++ b/source/blender/editors/Makefile @@ -29,6 +29,6 @@ # Bounces make to subdirectories. SOURCEDIR = source/blender/editors -DIRS = armature mesh animation object datafiles transform screen curve gpencil physics preview space_outliner space_time space_view3d interface util space_api space_ipo space_image space_node space_buttons space_info space_file space_sound space_action space_nla space_script space_text space_sequencer +DIRS = armature mesh animation object sculpt datafiles transform screen curve gpencil physics preview space_outliner space_time space_view3d interface util space_api space_ipo space_image space_node space_buttons space_info space_file space_sound space_action space_nla space_script space_text space_sequencer include nan_subdirs.mk diff --git a/source/blender/editors/SConscript b/source/blender/editors/SConscript index dba6044ea66..9c77d44945a 100644 --- a/source/blender/editors/SConscript +++ b/source/blender/editors/SConscript @@ -11,7 +11,7 @@ SConscript(['datafiles/SConscript', 'mesh/SConscript', 'object/SConscript', 'curve/SConscript', - #'gpencil/SConscript', enable this when there is actually code in gpencil/ + 'gpencil/SConscript', #'physics/SConscript', enable this when there is actually code in physics/ #'preview/SConscript', enable this when there is actually code in preview/ 'space_buttons/SConscript', diff --git a/source/blender/editors/gpencil/drawgpencil.c b/source/blender/editors/gpencil/drawgpencil.c new file mode 100644 index 00000000000..b467450673d --- /dev/null +++ b/source/blender/editors/gpencil/drawgpencil.c @@ -0,0 +1,1061 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008, Blender Foundation + * This is a new part of Blender + * + * Contributor(s): Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> +#include <math.h> +#include <float.h> + +#include "MEM_guardedalloc.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BMF_Api.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" + +#include "DNA_gpencil_types.h" +#include "DNA_listBase.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_blender.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_sequence.h" +#include "BKE_utildefines.h" + +#include "PIL_time.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "ED_gpencil.h" +#include "ED_sequencer.h" +#include "ED_util.h" + +#include "UI_interface.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "gpencil_intern.h" + +/* ************************************************** */ +/* GREASE PENCIL PANEL-UI DRAWING */ + +/* Every space which implements Grease-Pencil functionality should have a panel + * for the settings. All of the space-dependent parts should be coded in the panel + * code for that space, but the rest is all handled by generic panel here. + */ + +/* ------- Callbacks ----------- */ +/* These are just 'dummy wrappers' around gpencil api calls */ + +#if 0 +// XXX +/* make layer active one after being clicked on */ +void gp_ui_activelayer_cb (void *gpd, void *gpl) +{ + gpencil_layer_setactive(gpd, gpl); + + scrarea_queue_winredraw(curarea); + allqueue(REDRAWACTION, 0); +} + +/* rename layer and set active */ +void gp_ui_renamelayer_cb (void *gpd_arg, void *gpl_arg) +{ + bGPdata *gpd= (bGPdata *)gpd_arg; + bGPDlayer *gpl= (bGPDlayer *)gpl_arg; + + BLI_uniquename(&gpd->layers, gpl, "GP_Layer", offsetof(bGPDlayer, info[0]), 128); + gpencil_layer_setactive(gpd, gpl); + + scrarea_queue_winredraw(curarea); + allqueue(REDRAWACTION, 0); +} + +/* add a new layer */ +void gp_ui_addlayer_cb (void *gpd, void *dummy) +{ + gpencil_layer_addnew(gpd); + + scrarea_queue_winredraw(curarea); + allqueue(REDRAWACTION, 0); +} + +/* delete active layer */ +void gp_ui_dellayer_cb (void *gpd, void *dummy) +{ + gpencil_layer_delactive(gpd); + + scrarea_queue_winredraw(curarea); + allqueue(REDRAWACTION, 0); +} + +/* delete last stroke of active layer */ +void gp_ui_delstroke_cb (void *gpd, void *gpl) +{ + bGPDframe *gpf= gpencil_layer_getframe(gpl, CFRA, 0); + + gpencil_layer_setactive(gpd, gpl); + gpencil_frame_delete_laststroke(gpf); + + scrarea_queue_winredraw(curarea); +} + +/* delete active frame of active layer */ +void gp_ui_delframe_cb (void *gpd, void *gpl) +{ + bGPDframe *gpf= gpencil_layer_getframe(gpl, CFRA, 0); + + gpencil_layer_setactive(gpd, gpl); + gpencil_layer_delframe(gpl, gpf); + + scrarea_queue_winredraw(curarea); + allqueue(REDRAWACTION, 0); +} + +/* convert the active layer to geometry */ +void gp_ui_convertlayer_cb (void *gpd, void *gpl) +{ + gpencil_layer_setactive(gpd, gpl); + gpencil_convert_menu(); + + scrarea_queue_winredraw(curarea); +} +#endif + +/* ------- Drawing Code ------- */ + +#if 0 +/* XXX */ +/* draw the controls for a given layer */ +static void gp_drawui_layer (uiBlock *block, bGPdata *gpd, bGPDlayer *gpl, short *xco, short *yco) +{ + uiBut *but; + short active= (gpl->flag & GP_LAYER_ACTIVE); + short width= 314; + short height; + int rb_col; + + /* unless button has own callback, it adds this callback to button */ + uiBlockSetFunc(block, gp_ui_activelayer_cb, gpd, gpl); + + /* draw header */ + { + uiBlockSetEmboss(block, UI_EMBOSSN); + + /* rounded header */ + if (active) uiBlockSetCol(block, TH_BUT_ACTION); + rb_col= (active)?-20:20; + uiDefBut(block, ROUNDBOX, B_REDR, "", *xco-8, *yco-2, width, 24, NULL, 5.0, 0.0, 15.0, (float)(rb_col-20), ""); + if (active) uiBlockSetCol(block, TH_AUTO); + + /* lock toggle */ + uiDefIconButBitI(block, ICONTOG, GP_LAYER_LOCKED, B_REDR, ICON_UNLOCKED, *xco-7, *yco-1, 20, 20, &gpl->flag, 0.0, 0.0, 0, 0, "Layer cannot be modified"); + } + + /* when layer is locked or hidden, only draw header */ + if (gpl->flag & (GP_LAYER_LOCKED|GP_LAYER_HIDE)) { + char name[256]; /* gpl->info is 128, but we need space for 'locked/hidden' as well */ + + height= 0; + + /* visibility button (only if hidden but not locked!) */ + if ((gpl->flag & GP_LAYER_HIDE) && !(gpl->flag & GP_LAYER_LOCKED)) + uiDefIconButBitI(block, ICONTOG, GP_LAYER_HIDE, B_REDR, ICON_RESTRICT_VIEW_OFF, *xco+12, *yco-1, 20, 20, &gpl->flag, 0.0, 0.0, 0, 0, "Visibility of layer"); + + /* name */ + if (gpl->flag & GP_LAYER_HIDE) + sprintf(name, "%s (Hidden)", gpl->info); + else + sprintf(name, "%s (Locked)", gpl->info); + uiDefBut(block, LABEL, 1, name, *xco+35, *yco, 240, 20, NULL, 0.0, 0.0, 0, 0, "Short description of what this layer is for (optional)"); + + /* delete button (only if hidden but not locked!) */ + if ((gpl->flag & GP_LAYER_HIDE) & !(gpl->flag & GP_LAYER_LOCKED)) { + but= uiDefIconBut(block, BUT, B_REDR, ICON_X, *xco+(width-30), *yco, 19, 19, NULL, 0.0, 0.0, 0.0, 0.0, "Delete layer"); + uiButSetFunc(but, gp_ui_dellayer_cb, gpd, NULL); + } + uiBlockSetEmboss(block, UI_EMBOSS); + } + else { + height= 97; + + /* draw rest of header */ + { + /* visibility button */ + uiDefIconButBitI(block, ICONTOG, GP_LAYER_HIDE, B_REDR, ICON_RESTRICT_VIEW_OFF, *xco+12, *yco-1, 20, 20, &gpl->flag, 0.0, 0.0, 0, 0, "Visibility of layer"); + + uiBlockSetEmboss(block, UI_EMBOSS); + + /* name */ + but= uiDefButC(block, TEX, B_REDR, "Info:", *xco+36, *yco, 240, 19, gpl->info, 0, 127, 0, 0, "Short description of what this layer is for (optional)"); + uiButSetFunc(but, gp_ui_renamelayer_cb, gpd, gpl); + + /* delete 'button' */ + uiBlockSetEmboss(block, UI_EMBOSSN); + + but= uiDefIconBut(block, BUT, B_REDR, ICON_X, *xco+(width-30), *yco, 19, 19, NULL, 0.0, 0.0, 0.0, 0.0, "Delete layer"); + uiButSetFunc(but, gp_ui_dellayer_cb, gpd, NULL); + + uiBlockSetEmboss(block, UI_EMBOSS); + } + + /* draw backdrop */ + if (active) uiBlockSetCol(block, TH_BUT_ACTION); + uiDefBut(block, ROUNDBOX, B_DIFF, "", *xco-8, *yco-height, width, height-1, NULL, 5.0, 0.0, 12.0, (float)rb_col, ""); + if (active) uiBlockSetCol(block, TH_AUTO); + + /* draw settings */ + { + /* color */ + uiBlockBeginAlign(block); + uiDefButF(block, COL, B_REDR, "", *xco, *yco-26, 150, 19, gpl->color, 0, 0, 0, 0, "Color to use for all strokes on this Grease Pencil Layer"); + uiDefButF(block, NUMSLI, B_REDR, "Opacity: ", *xco,*yco-45,150,19, &gpl->color[3], 0.3f, 1.0f, 0, 0, "Visibility of stroke (0.3 to 1.0)"); + uiBlockEndAlign(block); + + /* stroke thickness */ + uiDefButS(block, NUMSLI, B_REDR, "Thickness:", *xco, *yco-75, 150, 20, &gpl->thickness, 1, 10, 0, 0, "Thickness of strokes (in pixels)"); + + /* debugging options */ + if (G.f & G_DEBUG) { + uiDefButBitI(block, TOG, GP_LAYER_DRAWDEBUG, B_REDR, "Show Points", *xco, *yco-95, 150, 20, &gpl->flag, 0, 0, 0, 0, "Show points which form the strokes"); + } + + /* onion-skinning */ + uiBlockBeginAlign(block); + uiDefButBitI(block, TOG, GP_LAYER_ONIONSKIN, B_REDR, "Onion-Skin", *xco+160, *yco-26, 140, 20, &gpl->flag, 0, 0, 0, 0, "Ghost frames on either side of frame"); + uiDefButS(block, NUMSLI, B_REDR, "GStep:", *xco+160, *yco-46, 140, 20, &gpl->gstep, 0, 120, 0, 0, "Max number of frames on either side of active frame to show (0 = just 'first' available sketch on either side)"); + uiBlockEndAlign(block); + + /* options */ + uiBlockBeginAlign(block); + if (curarea->spacetype == SPACE_VIEW3D) { + but= uiDefBut(block, BUT, B_REDR, "Convert to...", *xco+160, *yco-75, 140, 20, NULL, 0, 0, 0, 0, "Converts this layer's strokes to geometry (Hotkey = Alt-Shift-C)"); + uiButSetFunc(but, gp_ui_convertlayer_cb, gpd, gpl); + } + else { + but= uiDefBut(block, BUT, B_REDR, "Del Active Frame", *xco+160, *yco-75, 140, 20, NULL, 0, 0, 0, 0, "Erases the the active frame for this layer (Hotkey = Alt-XKEY/DEL)"); + uiButSetFunc(but, gp_ui_delframe_cb, gpd, gpl); + } + + but= uiDefBut(block, BUT, B_REDR, "Del Last Stroke", *xco+160, *yco-95, 140, 20, NULL, 0, 0, 0, 0, "Erases the last stroke from the active frame (Hotkey = Alt-XKEY/DEL)"); + uiButSetFunc(but, gp_ui_delstroke_cb, gpd, gpl); + uiBlockEndAlign(block); + } + } + + /* adjust height for new to start */ + (*yco) -= (height + 27); +} +#endif +/* Draw the contents for a grease-pencil panel. This assumes several things: + * - that panel has been created, is 318 x 204. max yco is 225 + * - that a toggle for turning on/off gpencil drawing is 150 x 20, starting from (10,225) + * which is basically the top left-hand corner + * It will return the amount of extra space to extend the panel by + */ +short draw_gpencil_panel (uiBlock *block, bGPdata *gpd, ScrArea *sa) +{ +#if 0 + uiBut *but; + bGPDlayer *gpl; + short xco= 10, yco= 170; + + /* draw gpd settings first */ + { + /* add new layer buttons */ + but= uiDefBut(block, BUT, B_REDR, "Add New Layer", 10,205,150,20, 0, 0, 0, 0, 0, "Adds a new Grease Pencil Layer"); + uiButSetFunc(but, gp_ui_addlayer_cb, gpd, NULL); + + + /* show override lmb-clicks button + painting lock */ + uiBlockBeginAlign(block); + if ((gpd->flag & GP_DATA_EDITPAINT)==0) { + uiDefButBitI(block, TOG, GP_DATA_EDITPAINT, B_REDR, "Draw Mode", 170, 225, 130, 20, &gpd->flag, 0, 0, 0, 0, "Interpret click-drag as new strokes"); + + uiBlockSetCol(block, TH_BUT_SETTING); + uiDefIconButBitI(block, ICONTOG, GP_DATA_LMBPLOCK, B_REDR, ICON_UNLOCKED, 300, 225, 20, 20, &gpd->flag, 0.0, 0.0, 0, 0, "Painting cannot occur with Shift-LMB (when making selections)"); + uiBlockSetCol(block, TH_AUTO); + } + else + uiDefButBitI(block, TOG, GP_DATA_EDITPAINT, B_REDR, "Draw Mode", 170, 225, 150, 20, &gpd->flag, 0, 0, 0, 0, "Interpret click-drag as new strokes"); + uiBlockEndAlign(block); + + /* 'view align' button (naming depends on context) */ + if (sa->spacetype == SPACE_VIEW3D) + uiDefButBitI(block, TOG, GP_DATA_VIEWALIGN, B_REDR, "Sketch in 3D", 170, 205, 150, 20, &gpd->flag, 0, 0, 0, 0, "New strokes are added in 3D-space"); + else + uiDefButBitI(block, TOG, GP_DATA_VIEWALIGN, B_REDR, "Stick to View", 170, 205, 150, 20, &gpd->flag, 0, 0, 0, 0, "New strokes are added on 2d-canvas"); + } + + /* draw for each layer */ + for (gpl= gpd->layers.first; gpl; gpl= gpl->next) { + gp_drawui_layer(block, gpd, gpl, &xco, &yco); + } + + /* return new height if necessary */ + return (yco < 0) ? (204 - yco) : 204; +#endif + return 0; +} + +/* ************************************************** */ +/* GREASE PENCIL DRAWING */ + +/* ----- General Defines ------ */ + +/* flags for sflag */ +enum { + GP_DRAWDATA_NOSTATUS = (1<<0), /* don't draw status info */ + GP_DRAWDATA_ONLY3D = (1<<1), /* only draw 3d-strokes */ + GP_DRAWDATA_ONLYV2D = (1<<2), /* only draw 'canvas' strokes */ + GP_DRAWDATA_ONLYI2D = (1<<3), /* only draw 'image' strokes */ +}; + +/* thickness above which we should use special drawing */ +#define GP_DRAWTHICKNESS_SPECIAL 3 + +/* ----- Tool Buffer Drawing ------ */ + +/* draw stroke defined in buffer (simple ogl lines/points for now, as dotted lines) */ +static void gp_draw_stroke_buffer (tGPspoint *points, int totpoints, short thickness, short dflag, short sflag) +{ + tGPspoint *pt; + int i; + + /* error checking */ + if ((points == NULL) || (totpoints <= 0)) + return; + + /* check if buffer can be drawn */ + if (dflag & (GP_DRAWDATA_ONLY3D|GP_DRAWDATA_ONLYV2D)) + return; + + /* if drawing a single point, draw it larger */ + if (totpoints == 1) { + /* draw point */ + glBegin(GL_POINTS); + glVertex2f(points->x, points->y); + glEnd(); + } + else if (sflag & GP_STROKE_ERASER) { + /* don't draw stroke at all! */ + } + else { + float oldpressure = 0.0f; + + /* draw stroke curve */ + if (G.f & G_DEBUG) setlinestyle(2); + + glBegin(GL_LINE_STRIP); + for (i=0, pt=points; i < totpoints && pt; i++, pt++) { + if (fabs(pt->pressure - oldpressure) > 0.2f) { + glEnd(); + glLineWidth(pt->pressure * thickness); + glBegin(GL_LINE_STRIP); + + glVertex2f(pt->x, pt->y); + + oldpressure = pt->pressure; + } + else + glVertex2f(pt->x, pt->y); + } + glEnd(); + + if (G.f & G_DEBUG) setlinestyle(0); + } +} + +/* ----- Existing Strokes Drawing (3D and Point) ------ */ + +/* draw a given stroke - just a single dot (only one point) */ +static void gp_draw_stroke_point (bGPDspoint *points, short thickness, short sflag, int offsx, int offsy, int winx, int winy) +{ + /* draw point */ + if (sflag & GP_STROKE_3DSPACE) { + glBegin(GL_POINTS); + glVertex3f(points->x, points->y, points->z); + glEnd(); + } + else { + int spacetype= 0; // XXX make local gpencil state var? + float co[2]; + + /* get coordinates of point */ + if (sflag & GP_STROKE_2DSPACE) { + co[0]= points->x; + co[1]= points->y; + } + else if (sflag & GP_STROKE_2DIMAGE) { + co[0]= (points->x * winx) + offsx; + co[1]= (points->y * winy) + offsy; + } + else { + co[0]= (points->x / 1000 * winx); + co[1]= (points->y / 1000 * winy); + } + + /* if thickness is less than GP_DRAWTHICKNESS_SPECIAL, simple dot looks ok + * - also mandatory in if Image Editor 'image-based' dot + */ + if ( (thickness < GP_DRAWTHICKNESS_SPECIAL) || + ((spacetype==SPACE_IMAGE) && (sflag & GP_STROKE_2DSPACE)) ) + { + glBegin(GL_POINTS); + glVertex2fv(co); + glEnd(); + } + else + { + /* draw filled circle as is done in circf (but without the matrix push/pops which screwed things up) */ + GLUquadricObj *qobj = gluNewQuadric(); + + gluQuadricDrawStyle(qobj, GLU_FILL); + + /* need to translate drawing position, but must reset after too! */ + glTranslatef(co[0], co[1], 0.); + gluDisk(qobj, 0.0, thickness, 32, 1); + glTranslatef(-co[0], -co[1], 0.); + + gluDeleteQuadric(qobj); + } + } +} + +/* draw a given stroke in 3d (i.e. in 3d-space), using simple ogl lines */ +static void gp_draw_stroke_3d (bGPDspoint *points, int totpoints, short thickness, short dflag, short sflag, short debug, int winx, int winy) +{ + bGPDspoint *pt; + float oldpressure = 0.0f; + int i; + + /* draw stroke curve */ + glBegin(GL_LINE_STRIP); + for (i=0, pt=points; i < totpoints && pt; i++, pt++) { + if (fabs(pt->pressure - oldpressure) > 0.2f) { + glEnd(); + glLineWidth(pt->pressure * thickness); + glBegin(GL_LINE_STRIP); + + glVertex3f(pt->x, pt->y, pt->z); + + oldpressure = pt->pressure; + } + else + glVertex3f(pt->x, pt->y, pt->z); + } + glEnd(); + + /* draw debug points of curve on top? */ + if (debug) { + glBegin(GL_POINTS); + for (i=0, pt=points; i < totpoints && pt; i++, pt++) + glVertex3f(pt->x, pt->y, pt->z); + glEnd(); + } +} + +/* ----- Fancy 2D-Stroke Drawing ------ */ + +/* draw a given stroke in 2d */ +static void gp_draw_stroke (bGPDspoint *points, int totpoints, short thickness, short dflag, short sflag, + short debug, int offsx, int offsy, int winx, int winy) +{ + int spacetype= 0; // XXX make local gpencil state var? + + /* if thickness is less than GP_DRAWTHICKNESS_SPECIAL, 'smooth' opengl lines look better + * - 'smooth' opengl lines are also required if Image Editor 'image-based' stroke + */ + if ( (thickness < GP_DRAWTHICKNESS_SPECIAL) || + ((spacetype==SPACE_IMAGE) && (dflag & GP_DRAWDATA_ONLYV2D)) ) + { + bGPDspoint *pt; + int i; + + glBegin(GL_LINE_STRIP); + for (i=0, pt=points; i < totpoints && pt; i++, pt++) { + if (sflag & GP_STROKE_2DSPACE) { + glVertex2f(pt->x, pt->y); + } + else if (sflag & GP_STROKE_2DIMAGE) { + const float x= (pt->x * winx) + offsx; + const float y= (pt->y * winy) + offsy; + + glVertex2f(x, y); + } + else { + const float x= (pt->x / 1000 * winx); + const float y= (pt->y / 1000 * winy); + + glVertex2f(x, y); + } + } + glEnd(); + } + + /* tesselation code - draw stroke as series of connected quads with connection + * edges rotated to minimise shrinking artifacts, and rounded endcaps + */ + else + { + bGPDspoint *pt1, *pt2; + float pm[2]; + int i; + + glShadeModel(GL_FLAT); + glBegin(GL_QUADS); + + for (i=0, pt1=points, pt2=points+1; i < (totpoints-1); i++, pt1++, pt2++) { + float s0[2], s1[2]; /* segment 'center' points */ + float t0[2], t1[2]; /* tesselated coordinates */ + float m1[2], m2[2]; /* gradient and normal */ + float mt[2], sc[2]; /* gradient for thickness, point for end-cap */ + float pthick; /* thickness at segment point */ + + /* get x and y coordinates from points */ + if (sflag & GP_STROKE_2DSPACE) { + s0[0]= pt1->x; s0[1]= pt1->y; + s1[0]= pt2->x; s1[1]= pt2->y; + } + else if (sflag & GP_STROKE_2DIMAGE) { + s0[0]= (pt1->x * winx) + offsx; + s0[1]= (pt1->y * winy) + offsy; + s1[0]= (pt2->x * winx) + offsx; + s1[1]= (pt2->y * winy) + offsy; + } + else { + s0[0]= (pt1->x / 1000 * winx); + s0[1]= (pt1->y / 1000 * winy); + s1[0]= (pt2->x / 1000 * winx); + s1[1]= (pt2->y / 1000 * winy); + } + + /* calculate gradient and normal - 'angle'=(ny/nx) */ + m1[1]= s1[1] - s0[1]; + m1[0]= s1[0] - s0[0]; + Normalize2(m1); + m2[1]= -m1[0]; + m2[0]= m1[1]; + + /* always use pressure from first point here */ + pthick= (pt1->pressure * thickness); + + /* if the first segment, start of segment is segment's normal */ + if (i == 0) { + /* draw start cap first + * - make points slightly closer to center (about halfway across) + */ + mt[0]= m2[0] * pthick * 0.5f; + mt[1]= m2[1] * pthick * 0.5f; + sc[0]= s0[0] - (m1[0] * pthick * 0.75f); + sc[1]= s0[1] - (m1[1] * pthick * 0.75f); + + t0[0]= sc[0] - mt[0]; + t0[1]= sc[1] - mt[1]; + t1[0]= sc[0] + mt[0]; + t1[1]= sc[1] + mt[1]; + + glVertex2fv(t0); + glVertex2fv(t1); + + /* calculate points for start of segment */ + mt[0]= m2[0] * pthick; + mt[1]= m2[1] * pthick; + + t0[0]= s0[0] - mt[0]; + t0[1]= s0[1] - mt[1]; + t1[0]= s0[0] + mt[0]; + t1[1]= s0[1] + mt[1]; + + /* draw this line twice (first to finish off start cap, then for stroke) */ + glVertex2fv(t1); + glVertex2fv(t0); + glVertex2fv(t0); + glVertex2fv(t1); + } + /* if not the first segment, use bisector of angle between segments */ + else { + float mb[2]; /* bisector normal */ + float athick, dfac; /* actual thickness, difference between thicknesses */ + + /* calculate gradient of bisector (as average of normals) */ + mb[0]= (pm[0] + m2[0]) / 2; + mb[1]= (pm[1] + m2[1]) / 2; + Normalize2(mb); + + /* calculate gradient to apply + * - as basis, use just pthick * bisector gradient + * - if cross-section not as thick as it should be, add extra padding to fix it + */ + mt[0]= mb[0] * pthick; + mt[1]= mb[1] * pthick; + athick= Vec2Length(mt); + dfac= pthick - (athick * 2); + if ( ((athick * 2) < pthick) && (IS_EQ(athick, pthick)==0) ) + { + mt[0] += (mb[0] * dfac); + mt[1] += (mb[1] * dfac); + } + + /* calculate points for start of segment */ + t0[0]= s0[0] - mt[0]; + t0[1]= s0[1] - mt[1]; + t1[0]= s0[0] + mt[0]; + t1[1]= s0[1] + mt[1]; + + /* draw this line twice (once for end of current segment, and once for start of next) */ + glVertex2fv(t1); + glVertex2fv(t0); + glVertex2fv(t0); + glVertex2fv(t1); + } + + /* if last segment, also draw end of segment (defined as segment's normal) */ + if (i == totpoints-2) { + /* for once, we use second point's pressure (otherwise it won't be drawn) */ + pthick= (pt2->pressure * thickness); + + /* calculate points for end of segment */ + mt[0]= m2[0] * pthick; + mt[1]= m2[1] * pthick; + + t0[0]= s1[0] - mt[0]; + t0[1]= s1[1] - mt[1]; + t1[0]= s1[0] + mt[0]; + t1[1]= s1[1] + mt[1]; + + /* draw this line twice (once for end of stroke, and once for endcap)*/ + glVertex2fv(t1); + glVertex2fv(t0); + glVertex2fv(t0); + glVertex2fv(t1); + + + /* draw end cap as last step + * - make points slightly closer to center (about halfway across) + */ + mt[0]= m2[0] * pthick * 0.5f; + mt[1]= m2[1] * pthick * 0.5f; + sc[0]= s1[0] + (m1[0] * pthick * 0.75f); + sc[1]= s1[1] + (m1[1] * pthick * 0.75f); + + t0[0]= sc[0] - mt[0]; + t0[1]= sc[1] - mt[1]; + t1[0]= sc[0] + mt[0]; + t1[1]= sc[1] + mt[1]; + + glVertex2fv(t1); + glVertex2fv(t0); + } + + /* store stroke's 'natural' normal for next stroke to use */ + Vec2Copyf(pm, m2); + } + + glEnd(); + } + + /* draw debug points of curve on top? (original stroke points) */ + if (debug) { + bGPDspoint *pt; + int i; + + glBegin(GL_POINTS); + for (i=0, pt=points; i < totpoints && pt; i++, pt++) { + if (sflag & GP_STROKE_2DSPACE) { + glVertex2f(pt->x, pt->y); + } + else if (sflag & GP_STROKE_2DIMAGE) { + const float x= (float)((pt->x * winx) + offsx); + const float y= (float)((pt->y * winy) + offsy); + + glVertex2f(x, y); + } + else { + const float x= (float)(pt->x / 1000 * winx); + const float y= (float)(pt->y / 1000 * winy); + + glVertex2f(x, y); + } + } + glEnd(); + } +} + +/* ----- General Drawing ------ */ + +/* draw a set of strokes */ +static void gp_draw_strokes (bGPDframe *gpf, int offsx, int offsy, int winx, int winy, int dflag, + short debug, short lthick, float color[4]) +{ + bGPDstroke *gps; + + /* set color first (may need to reset it again later too) */ + glColor4f(color[0], color[1], color[2], color[3]); + + for (gps= gpf->strokes.first; gps; gps= gps->next) { + /* check if stroke can be drawn - checks here generally fall into pairs */ + if ((dflag & GP_DRAWDATA_ONLY3D) && !(gps->flag & GP_STROKE_3DSPACE)) + continue; + if (!(dflag & GP_DRAWDATA_ONLY3D) && (gps->flag & GP_STROKE_3DSPACE)) + continue; + if ((dflag & GP_DRAWDATA_ONLYV2D) && !(gps->flag & GP_STROKE_2DSPACE)) + continue; + if (!(dflag & GP_DRAWDATA_ONLYV2D) && (gps->flag & GP_STROKE_2DSPACE)) + continue; + if ((dflag & GP_DRAWDATA_ONLYI2D) && !(gps->flag & GP_STROKE_2DIMAGE)) + continue; + if (!(dflag & GP_DRAWDATA_ONLYI2D) && (gps->flag & GP_STROKE_2DIMAGE)) + continue; + if ((gps->points == 0) || (gps->totpoints < 1)) + continue; + + /* check which stroke-drawer to use */ + if (gps->totpoints == 1) + gp_draw_stroke_point(gps->points, lthick, gps->flag, offsx, offsy, winx, winy); + else if (dflag & GP_DRAWDATA_ONLY3D) + gp_draw_stroke_3d(gps->points, gps->totpoints, lthick, dflag, gps->flag, debug, winx, winy); + else if (gps->totpoints > 1) + gp_draw_stroke(gps->points, gps->totpoints, lthick, dflag, gps->flag, debug, offsx, offsy, winx, winy); + } +} + +/* draw grease-pencil datablock */ +static void gp_draw_data (bGPdata *gpd, int offsx, int offsy, int winx, int winy, int cfra, int dflag) +{ + bGPDlayer *gpl, *actlay=NULL; + + /* turn on smooth lines (i.e. anti-aliasing) */ + glEnable(GL_LINE_SMOOTH); + + /* turn on alpha-blending */ + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + /* loop over layers, drawing them */ + for (gpl= gpd->layers.first; gpl; gpl= gpl->next) { + bGPDframe *gpf; + + short debug = (gpl->flag & GP_LAYER_DRAWDEBUG) ? 1 : 0; + short lthick= gpl->thickness; + float color[4], tcolor[4]; + + /* don't draw layer if hidden */ + if (gpl->flag & GP_LAYER_HIDE) + continue; + + /* if layer is active one, store pointer to it */ + if (gpl->flag & GP_LAYER_ACTIVE) + actlay= gpl; + + /* get frame to draw */ + gpf= gpencil_layer_getframe(gpl, cfra, 0); + if (gpf == NULL) + continue; + + /* set color, stroke thickness, and point size */ + glLineWidth(lthick); + QUATCOPY(color, gpl->color); // just for copying 4 array elements + QUATCOPY(tcolor, gpl->color); // additional copy of color (for ghosting) + glColor4f(color[0], color[1], color[2], color[3]); + glPointSize((float)(gpl->thickness + 2)); + + /* draw 'onionskins' (frame left + right) */ + if (gpl->flag & GP_LAYER_ONIONSKIN) { + /* drawing method - only immediately surrounding (gstep = 0), or within a frame range on either side (gstep > 0)*/ + if (gpl->gstep) { + bGPDframe *gf; + float fac; + + /* draw previous frames first */ + for (gf=gpf->prev; gf; gf=gf->prev) { + /* check if frame is drawable */ + if ((gpf->framenum - gf->framenum) <= gpl->gstep) { + /* alpha decreases with distance from curframe index */ + fac= (float)(gpf->framenum - gf->framenum) / (float)gpl->gstep; + tcolor[3] = color[3] - fac; + gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor); + } + else + break; + } + + /* now draw next frames */ + for (gf= gpf->next; gf; gf=gf->next) { + /* check if frame is drawable */ + if ((gf->framenum - gpf->framenum) <= gpl->gstep) { + /* alpha decreases with distance from curframe index */ + fac= (float)(gf->framenum - gpf->framenum) / (float)gpl->gstep; + tcolor[3] = color[3] - fac; + gp_draw_strokes(gf, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor); + } + else + break; + } + + /* restore alpha */ + glColor4f(color[0], color[1], color[2], color[3]); + } + else { + /* draw the strokes for the ghost frames (at half of the alpha set by user) */ + if (gpf->prev) { + tcolor[3] = (color[3] / 7); + gp_draw_strokes(gpf->prev, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor); + } + + if (gpf->next) { + tcolor[3] = (color[3] / 4); + gp_draw_strokes(gpf->next, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor); + } + + /* restore alpha */ + glColor4f(color[0], color[1], color[2], color[3]); + } + } + + /* draw the strokes already in active frame */ + tcolor[3]= color[3]; + gp_draw_strokes(gpf, offsx, offsy, winx, winy, dflag, debug, lthick, tcolor); + + /* Check if may need to draw the active stroke cache, only if this layer is the active layer + * that is being edited. (Stroke buffer is currently stored in gp-data) + */ + if ((G.f & G_GREASEPENCIL) && (gpl->flag & GP_LAYER_ACTIVE) && + (gpf->flag & GP_FRAME_PAINT)) + { + /* Buffer stroke needs to be drawn with a different linestyle to help differentiate them from normal strokes. */ + gp_draw_stroke_buffer(gpd->sbuffer, gpd->sbuffer_size, lthick, dflag, gpd->sbuffer_sflag); + } + } + + /* turn off alpha blending, then smooth lines */ + glDisable(GL_BLEND); // alpha blending + glDisable(GL_LINE_SMOOTH); // smooth lines + + /* show info for debugging the status of gpencil */ + if ( ((dflag & GP_DRAWDATA_NOSTATUS)==0) && (gpd->flag & GP_DATA_DISPINFO) ) { + char printable[256]; + short xmax; + + /* get text to display */ + if (actlay) { + if (gpd->flag & GP_DATA_EDITPAINT) + UI_ThemeColor(TH_BONE_POSE); // should be blue-ish + else if (actlay->actframe == NULL) + UI_ThemeColor(TH_REDALERT); + else if (actlay->actframe->framenum == cfra) + UI_ThemeColor(TH_VERTEX_SELECT); // should be yellow + else + UI_ThemeColor(TH_TEXT_HI); + + if (actlay->actframe) { + sprintf(printable, "GPencil: Layer ('%s'), Frame (%d)%s", + actlay->info, actlay->actframe->framenum, + ((gpd->flag & GP_DATA_EDITPAINT)?" , Draw Mode On":"") ); + } + else { + sprintf(printable, "GPencil: Layer ('%s'), Frame <None>%s", + actlay->info, ((gpd->flag & GP_DATA_EDITPAINT)?" , Draw Mode On":"") ); + } + } + else { + UI_ThemeColor(TH_REDALERT); + sprintf(printable, "GPencil: Layer <None>"); + } + xmax= GetButStringLength(printable); + + /* only draw it if view is wide enough (assume padding of 20 is enough for now) */ + if (winx > (xmax + 20)) { + glRasterPos2i(winx-xmax, winy-20); + BMF_DrawString(G.fonts, printable); + } + } + + /* restore initial gl conditions */ + glLineWidth(1.0); + glPointSize(1.0); + glColor4f(0, 0, 0, 1); +} + +/* ----- Grease Pencil Sketches Drawing API ------ */ + +/* draw grease-pencil sketches to specified 2d-view that uses ibuf corrections */ +void draw_gpencil_2dimage (bContext *C, ImBuf *ibuf) +{ + ScrArea *sa= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + Scene *scene= CTX_data_scene(C); + bGPdata *gpd; + int offsx, offsy, sizex, sizey; + int dflag = GP_DRAWDATA_NOSTATUS; + + /* check that we have grease-pencil stuff to draw */ + if (ELEM(NULL, sa, ibuf)) return; + gpd= gpencil_data_getactive(sa); + if (gpd == NULL) return; + + /* calculate rect */ + switch (sa->spacetype) { + case SPACE_IMAGE: /* image */ + { + + /* just draw using standard scaling (settings here are currently ignored anyways) */ + // FIXME: the opengl poly-strokes don't draw at right thickness when done this way, so disabled + offsx= 0; + offsy= 0; + sizex= ar->winx; + sizey= ar->winy; + + wmOrtho2(ar->v2d.cur.xmin, ar->v2d.cur.xmax, ar->v2d.cur.ymin, ar->v2d.cur.ymax); + + dflag |= GP_DRAWDATA_ONLYV2D; + } + break; + + case SPACE_SEQ: /* sequence */ + { + SpaceSeq *sseq= (SpaceSeq *)sa->spacedata.first; + float zoom, zoomx, zoomy; + + /* calculate accessory values */ + zoom= (float)(SEQ_ZOOM_FAC(sseq->zoom)); + if (sseq->mainb == SEQ_DRAW_IMG_IMBUF) { + /* XXX sequencer zoom should store it? */ + zoomx = zoom; // * ((float)G.scene->r.xasp / (float)G.scene->r.yasp); + zoomy = zoom; + } + else + zoomx = zoomy = zoom; + + /* calculate transforms (Note: we use ibuf here, as we have it) */ + sizex= (int)(zoomx * ibuf->x); + sizey= (int)(zoomy * ibuf->y); + offsx= (int)( (ar->winx-sizex)/2 + sseq->xof ); + offsy= (int)( (ar->winy-sizey)/2 + sseq->yof ); + + dflag |= GP_DRAWDATA_ONLYI2D; + } + break; + + default: /* for spacetype not yet handled */ + offsx= 0; + offsy= 0; + sizex= ar->winx; + sizey= ar->winy; + + dflag |= GP_DRAWDATA_ONLYI2D; + break; + } + + + /* draw it! */ + gp_draw_data(gpd, offsx, offsy, sizex, sizey, CFRA, dflag); +} + +/* draw grease-pencil sketches to specified 2d-view assuming that matrices are already set correctly + * Note: this gets called twice - first time with onlyv2d=1 to draw 'canvas' strokes, second time with onlyv2d=0 for screen-aligned strokes + */ +void draw_gpencil_2dview (bContext *C, short onlyv2d) +{ + ScrArea *sa= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + Scene *scene= CTX_data_scene(C); + bGPdata *gpd; + int dflag = 0; + + /* check that we have grease-pencil stuff to draw */ + if (sa == NULL) return; + gpd= gpencil_data_getactive(sa); + if (gpd == NULL) return; + + /* draw it! */ + if (onlyv2d) dflag |= (GP_DRAWDATA_ONLYV2D|GP_DRAWDATA_NOSTATUS); + gp_draw_data(gpd, 0, 0, ar->winx, ar->winy, CFRA, dflag); +} + +/* draw grease-pencil sketches to specified 3d-view assuming that matrices are already set correctly + * Note: this gets called twice - first time with only3d=1 to draw 3d-strokes, second time with only3d=0 for screen-aligned strokes + */ +void draw_gpencil_3dview (bContext *C, short only3d) +{ + ScrArea *sa= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + Scene *scene= CTX_data_scene(C); + bGPdata *gpd; + int dflag = 0; + + /* check that we have grease-pencil stuff to draw */ + gpd= gpencil_data_getactive(sa); + if (gpd == NULL) return; + + /* draw it! */ + if (only3d) dflag |= (GP_DRAWDATA_ONLY3D|GP_DRAWDATA_NOSTATUS); + gp_draw_data(gpd, 0, 0, ar->winx, ar->winy, CFRA, dflag); +} + +/* draw grease-pencil sketches to opengl render window assuming that matrices are already set correctly */ +// XXX porting note, ogl render will probably be a window with one 3d region +void draw_gpencil_oglrender (bContext *C) +{ + ScrArea *sa= CTX_wm_area(C); + View3D *v3d= (View3D *)sa->spacedata.first; + ARegion *ar= CTX_wm_region(C); + Scene *scene= CTX_data_scene(C); + bGPdata *gpd; + + /* assume gpencil data comes from v3d */ + if (v3d == NULL) return; + gpd= v3d->gpd; + if (gpd == NULL) return; + + /* pass 1: draw 3d-strokes ------------ > */ + gp_draw_data(gpd, 0, 0, ar->winx, ar->winy, CFRA, (GP_DRAWDATA_NOSTATUS|GP_DRAWDATA_ONLY3D)); + + /* pass 2: draw 2d-strokes ------------ > */ + /* adjust view matrices */ + wmOrtho2(-0.375f, (float)(ar->winx)-0.375f, -0.375f, (float)(ar->winy)-0.375f); + glLoadIdentity(); + + /* draw it! */ + gp_draw_data(gpd, 0, 0, ar->winx, ar->winy, CFRA, GP_DRAWDATA_NOSTATUS); +} + +/* ************************************************** */ diff --git a/source/blender/editors/gpencil/editaction_gpencil.c b/source/blender/editors/gpencil/editaction_gpencil.c new file mode 100644 index 00000000000..79057b95fd1 --- /dev/null +++ b/source/blender/editors/gpencil/editaction_gpencil.c @@ -0,0 +1,739 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008, Blender Foundation + * This is a new part of Blender + * + * Contributor(s): Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> +#include <math.h> + +#include "MEM_guardedalloc.h" + +#include "BMF_Api.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" + +#include "DNA_listBase.h" +#include "DNA_action_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_global.h" +#include "BKE_utildefines.h" +#include "BKE_blender.h" +#include "BKE_ipo.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "PIL_time.h" + +#include "ED_anim_api.h" +#include "ED_keyframes_edit.h" +#include "ED_keyframes_draw.h" +#include "ED_gpencil.h" +#include "ED_util.h" +#include "ED_types.h" + +#include "gpencil_intern.h" + +/* XXX */ +static void actdata_filter() {} +static void BIF_undo_push() {} +static void error() {} +static void *get_action_context() {return NULL;} +static int find_nearest_marker_time() {return 1;} +/* XXX */ + + +/* ***************************************** */ +/* NOTE ABOUT THIS FILE: + * This file contains code for editing Grease Pencil data in the Action Editor + * as a 'keyframes', so that a user can adjust the timing of Grease Pencil drawings. + * Therefore, this file mostly contains functions for selecting Grease-Pencil frames. + */ +/* ***************************************** */ +/* Generics - Loopers */ + +/* Loops over the gp-frames for a gp-layer, and applies the given callback */ +short gplayer_frames_looper (bGPDlayer *gpl, Scene *scene, short (*gpf_cb)(bGPDframe *, Scene *)) +{ + bGPDframe *gpf; + + /* error checker */ + if (gpl == NULL) + return 0; + + /* do loop */ + for (gpf= gpl->frames.first; gpf; gpf= gpf->next) { + /* execute callback */ + if (gpf_cb(gpf, scene)) + return 1; + } + + /* nothing to return */ + return 0; +} + +/* ****************************************** */ +/* Data Conversion Tools */ + +/* make a listing all the gp-frames in a layer as cfraelems */ +void gplayer_make_cfra_list (bGPDlayer *gpl, ListBase *elems, short onlysel) +{ + bGPDframe *gpf; + CfraElem *ce; + + /* error checking */ + if (ELEM(NULL, gpl, elems)) + return; + + /* loop through gp-frames, adding */ + for (gpf= gpl->frames.first; gpf; gpf= gpf->next) { + if ((onlysel == 0) || (gpf->flag & GP_FRAME_SELECT)) { + ce= MEM_callocN(sizeof(CfraElem), "CfraElem"); + + ce->cfra= (float)gpf->framenum; + ce->sel= (gpf->flag & GP_FRAME_SELECT) ? 1 : 0; + + BLI_addtail(elems, ce); + } + } +} + +/* ***************************************** */ +/* Selection Tools */ + +/* check if one of the frames in this layer is selected */ +short is_gplayer_frame_selected (bGPDlayer *gpl) +{ + bGPDframe *gpf; + + /* error checking */ + if (gpl == NULL) + return 0; + + /* stop at the first one found */ + for (gpf= gpl->frames.first; gpf; gpf= gpf->next) { + if (gpf->flag & GP_FRAME_SELECT) + return 1; + } + + /* not found */ + return 0; +} + +/* helper function - select gp-frame based on SELECT_* mode */ +static void gpframe_select (bGPDframe *gpf, short select_mode) +{ + switch (select_mode) { + case SELECT_ADD: + gpf->flag |= GP_FRAME_SELECT; + break; + case SELECT_SUBTRACT: + gpf->flag &= ~GP_FRAME_SELECT; + break; + case SELECT_INVERT: + gpf->flag ^= GP_FRAME_SELECT; + break; + } +} + +/* set all/none/invert select (like above, but with SELECT_* modes) */ +void select_gpencil_frames (bGPDlayer *gpl, short select_mode) +{ + bGPDframe *gpf; + + /* error checking */ + if (gpl == NULL) + return; + + /* handle according to mode */ + for (gpf= gpl->frames.first; gpf; gpf= gpf->next) { + gpframe_select(gpf, select_mode); + } +} + +/* set all/none/invert select */ +void set_gplayer_frame_selection (bGPDlayer *gpl, short mode) +{ + /* error checking */ + if (gpl == NULL) + return; + + /* convert mode to select_mode */ + switch (mode) { + case 2: + mode= SELECT_INVERT; + break; + case 1: + mode= SELECT_ADD; + break; + case 0: + mode= SELECT_SUBTRACT; + break; + default: + return; + } + + /* now call the standard function */ + select_gpencil_frames (gpl, mode); +} + +/* select the frame in this layer that occurs on this frame (there should only be one at most) */ +void select_gpencil_frame (bGPDlayer *gpl, int selx, short select_mode) +{ + bGPDframe *gpf; + + /* search through frames for a match */ + for (gpf= gpl->frames.first; gpf; gpf= gpf->next) { + /* there should only be one frame with this frame-number */ + if (gpf->framenum == selx) { + gpframe_select(gpf, select_mode); + break; + } + } +} + +/* select the frames in this layer that occur within the bounds specified */ +void borderselect_gplayer_frames (bGPDlayer *gpl, float min, float max, short select_mode) +{ + bGPDframe *gpf; + + /* only select those frames which are in bounds */ + for (gpf= gpl->frames.first; gpf; gpf= gpf->next) { + if (IN_RANGE(gpf->framenum, min, max)) + gpframe_select(gpf, select_mode); + } +} + + +/* De-selects or inverts the selection of Layers for a grease-pencil block + * mode: 0 = default behaviour (select all), 1 = test if (de)select all, 2 = invert all + */ +void deselect_gpencil_layers (void *data, short mode) +{ + ListBase act_data = {NULL, NULL}; + bActListElem *ale; + int filter, sel=1; + + /* filter data */ + filter= ACTFILTER_VISIBLE; + actdata_filter(&act_data, filter, data, ACTCONT_GPENCIL); + + /* See if we should be selecting or deselecting */ + if (mode == 1) { + for (ale= act_data.first; ale; ale= ale->next) { + if (sel == 0) + break; + + if (ale->flag & GP_LAYER_SELECT) + sel= 0; + } + } + else + sel= 0; + + /* Now set the flags */ + for (ale= act_data.first; ale; ale= ale->next) { + bGPDlayer *gpl= (bGPDlayer *)ale->data; + + if (mode == 2) + gpl->flag ^= GP_LAYER_SELECT; + else if (sel) + gpl->flag |= GP_LAYER_SELECT; + else + gpl->flag &= ~GP_LAYER_SELECT; + + gpl->flag &= ~GP_LAYER_ACTIVE; + } + + /* Cleanup */ + BLI_freelistN(&act_data); +} + +/* ***************************************** */ +/* Frame Editing Tools */ + +/* Delete selected grease-pencil layers */ +void delete_gpencil_layers (void) +{ + ListBase act_data = {NULL, NULL}; + bActListElem *ale, *next; + void *data; + short datatype; + int filter; + + /* determine what type of data we are operating on */ + data = get_action_context(&datatype); + if (data == NULL) return; + if (datatype != ACTCONT_GPENCIL) return; + + /* filter data */ + filter= (ACTFILTER_VISIBLE | ACTFILTER_FOREDIT | ACTFILTER_CHANNELS | ACTFILTER_SEL); + actdata_filter(&act_data, filter, data, datatype); + + /* clean up grease-pencil layers */ + for (ale= act_data.first; ale; ale= next) { + bGPdata *gpd= (bGPdata *)ale->owner; + bGPDlayer *gpl= (bGPDlayer *)ale->data; + next= ale->next; + + /* free layer and its data */ + if (SEL_GPL(gpl)) { + free_gpencil_frames(gpl); + BLI_freelinkN(&gpd->layers, gpl); + } + + /* free temp memory */ + BLI_freelinkN(&act_data, ale); + } + + BIF_undo_push("Delete GPencil Layers"); +} + +/* Delete selected frames */ +void delete_gplayer_frames (bGPDlayer *gpl) +{ + bGPDframe *gpf, *gpfn; + + /* error checking */ + if (gpl == NULL) + return; + + /* check for frames to delete */ + for (gpf= gpl->frames.first; gpf; gpf= gpfn) { + gpfn= gpf->next; + + if (gpf->flag & GP_FRAME_SELECT) + gpencil_layer_delframe(gpl, gpf); + } +} + +/* Duplicate selected frames from given gp-layer */ +void duplicate_gplayer_frames (bGPDlayer *gpl) +{ + bGPDframe *gpf, *gpfn; + + /* error checking */ + if (gpl == NULL) + return; + + /* duplicate selected frames */ + for (gpf= gpl->frames.first; gpf; gpf= gpfn) { + gpfn= gpf->next; + + /* duplicate this frame */ + if (gpf->flag & GP_FRAME_SELECT) { + bGPDframe *gpfd; + + /* duplicate frame, and deselect self */ + gpfd= gpencil_frame_duplicate(gpf); + gpf->flag &= ~GP_FRAME_SELECT; + + BLI_insertlinkafter(&gpl->frames, gpf, gpfd); + } + } +} + +/* -------------------------------------- */ +/* Copy and Paste Tools */ +/* - The copy/paste buffer currently stores a set of GP_Layers, with temporary + * GP_Frames with the necessary strokes + * - Unless there is only one element in the buffer, names are also tested to check for compatability. + * - All pasted frames are offset by the same amount. This is calculated as the difference in the times of + * the current frame and the 'first keyframe' (i.e. the earliest one in all channels). + * - The earliest frame is calculated per copy operation. + */ + +/* globals for copy/paste data (like for other copy/paste buffers) */ +ListBase gpcopybuf = {NULL, NULL}; +static int gpcopy_firstframe= 999999999; + +/* This function frees any MEM_calloc'ed copy/paste buffer data */ +void free_gpcopybuf () +{ + free_gpencil_layers(&gpcopybuf); + + gpcopybuf.first= gpcopybuf.last= NULL; + gpcopy_firstframe= 999999999; +} + +/* This function adds data to the copy/paste buffer, freeing existing data first + * Only the selected GP-layers get their selected keyframes copied. + */ +void copy_gpdata () +{ + ListBase act_data = {NULL, NULL}; + bActListElem *ale; + int filter; + void *data; + short datatype; + + /* clear buffer first */ + free_gpcopybuf(); + + /* get data */ + data= get_action_context(&datatype); + if (data == NULL) return; + if (datatype != ACTCONT_GPENCIL) return; + + /* filter data */ + filter= (ACTFILTER_VISIBLE | ACTFILTER_SEL); + actdata_filter(&act_data, filter, data, datatype); + + /* assume that each of these is an ipo-block */ + for (ale= act_data.first; ale; ale= ale->next) { + bGPDlayer *gpls, *gpln; + bGPDframe *gpf, *gpfn; + + /* get new layer to put into buffer */ + gpls= (bGPDlayer *)ale->data; + gpln= MEM_callocN(sizeof(bGPDlayer), "GPCopyPasteLayer"); + + gpln->frames.first= gpln->frames.last= NULL; + strcpy(gpln->info, gpls->info); + + BLI_addtail(&gpcopybuf, gpln); + + /* loop over frames, and copy only selected frames */ + for (gpf= gpls->frames.first; gpf; gpf= gpf->next) { + /* if frame is selected, make duplicate it and its strokes */ + if (gpf->flag & GP_FRAME_SELECT) { + /* add frame to buffer */ + gpfn= gpencil_frame_duplicate(gpf); + BLI_addtail(&gpln->frames, gpfn); + + /* check if this is the earliest frame encountered so far */ + if (gpf->framenum < gpcopy_firstframe) + gpcopy_firstframe= gpf->framenum; + } + } + } + + /* check if anything ended up in the buffer */ + if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) + error("Nothing copied to buffer"); + + /* free temp memory */ + BLI_freelistN(&act_data); +} + +void paste_gpdata (Scene *scene) +{ + ListBase act_data = {NULL, NULL}; + bActListElem *ale; + int filter; + void *data; + short datatype; + + const int offset = (CFRA - gpcopy_firstframe); + short no_name= 0; + + /* check if buffer is empty */ + if (ELEM(NULL, gpcopybuf.first, gpcopybuf.last)) { + error("No data in buffer to paste"); + return; + } + /* check if single channel in buffer (disregard names if so) */ + if (gpcopybuf.first == gpcopybuf.last) + no_name= 1; + + /* get data */ + data= get_action_context(&datatype); + if (data == NULL) return; + if (datatype != ACTCONT_GPENCIL) return; + + /* filter data */ + filter= (ACTFILTER_VISIBLE | ACTFILTER_SEL | ACTFILTER_FOREDIT); + actdata_filter(&act_data, filter, data, datatype); + + /* from selected channels */ + for (ale= act_data.first; ale; ale= ale->next) { + bGPDlayer *gpld= (bGPDlayer *)ale->data; + bGPDlayer *gpls= NULL; + bGPDframe *gpfs, *gpf; + + /* find suitable layer from buffer to use to paste from */ + for (gpls= gpcopybuf.first; gpls; gpls= gpls->next) { + /* check if layer name matches */ + if ((no_name) || (strcmp(gpls->info, gpld->info)==0)) + break; + } + + /* this situation might occur! */ + if (gpls == NULL) + continue; + + /* add frames from buffer */ + for (gpfs= gpls->frames.first; gpfs; gpfs= gpfs->next) { + /* temporarily apply offset to buffer-frame while copying */ + gpfs->framenum += offset; + + /* get frame to copy data into (if no frame returned, then just ignore) */ + gpf= gpencil_layer_getframe(gpld, gpfs->framenum, 1); + if (gpf) { + bGPDstroke *gps, *gpsn; + ScrArea *sa; + + /* get area that gp-data comes from */ + sa= gpencil_data_findowner((bGPdata *)ale->owner); + + /* this should be the right frame... as it may be a pre-existing frame, + * must make sure that only compatible stroke types get copied over + * - we cannot just add a duplicate frame, as that would cause errors + * - need to check for compatible types to minimise memory usage (copying 'junk' over) + */ + for (gps= gpfs->strokes.first; gps; gps= gps->next) { + short stroke_ok; + + /* if there's an area, check that it supports this type of stroke */ + if (sa) { + stroke_ok= 0; + + /* check if spacetype supports this type of stroke + * - NOTE: must sync this with gp_paint_initstroke() in gpencil.c + */ + switch (sa->spacetype) { + case SPACE_VIEW3D: /* 3D-View: either screen-aligned or 3d-space */ + if ((gps->flag == 0) || (gps->flag & GP_STROKE_3DSPACE)) + stroke_ok= 1; + break; + + case SPACE_NODE: /* Nodes Editor: either screen-aligned or view-aligned */ + case SPACE_IMAGE: /* Image Editor: either screen-aligned or view\image-aligned */ + if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DSPACE)) + stroke_ok= 1; + break; + + case SPACE_SEQ: /* Sequence Editor: either screen-aligned or view-aligned */ + if ((gps->flag == 0) || (gps->flag & GP_STROKE_2DIMAGE)) + stroke_ok= 1; + break; + } + } + else + stroke_ok= 1; + + /* if stroke is ok, we make a copy of this stroke and add to frame */ + if (stroke_ok) { + /* make a copy of stroke, then of its points array */ + gpsn= MEM_dupallocN(gps); + gpsn->points= MEM_dupallocN(gps->points); + + /* append stroke to frame */ + BLI_addtail(&gpf->strokes, gpsn); + } + } + + /* if no strokes (i.e. new frame) added, free gpf */ + if (gpf->strokes.first == NULL) + gpencil_layer_delframe(gpld, gpf); + } + + /* unapply offset from buffer-frame */ + gpfs->framenum -= offset; + } + } + + /* free temp memory */ + BLI_freelistN(&act_data); + + /* undo and redraw stuff */ + BIF_undo_push("Paste Grease Pencil Frames"); +} + +/* -------------------------------------- */ +/* Snap Tools */ + +static short snap_gpf_nearest (bGPDframe *gpf, Scene *scene) +{ + if (gpf->flag & GP_FRAME_SELECT) + gpf->framenum= (int)(floor(gpf->framenum+0.5)); + return 0; +} + +static short snap_gpf_nearestsec (bGPDframe *gpf, Scene *scene) +{ + float secf = (float)FPS; + if (gpf->flag & GP_FRAME_SELECT) + gpf->framenum= (int)(floor(gpf->framenum/secf + 0.5f) * secf); + return 0; +} + +static short snap_gpf_cframe (bGPDframe *gpf, Scene *scene) +{ + if (gpf->flag & GP_FRAME_SELECT) + gpf->framenum= (int)CFRA; + return 0; +} + +static short snap_gpf_nearmarker (bGPDframe *gpf, Scene *scene) +{ + if (gpf->flag & GP_FRAME_SELECT) + gpf->framenum= (int)find_nearest_marker_time((float)gpf->framenum); + return 0; +} + + +/* snap selected frames to ... */ +void snap_gplayer_frames (bGPDlayer *gpl, Scene *scene, short mode) +{ + switch (mode) { + case 1: /* snap to nearest frame */ + gplayer_frames_looper(gpl, scene, snap_gpf_nearest); + break; + case 2: /* snap to current frame */ + gplayer_frames_looper(gpl, scene, snap_gpf_cframe); + break; + case 3: /* snap to nearest marker */ + gplayer_frames_looper(gpl, scene, snap_gpf_nearmarker); + break; + case 4: /* snap to nearest second */ + gplayer_frames_looper(gpl, scene, snap_gpf_nearestsec); + break; + default: /* just in case */ + gplayer_frames_looper(gpl, scene, snap_gpf_nearest); + break; + } +} + +/* -------------------------------------- */ +/* Mirror Tools */ + +static short mirror_gpf_cframe (bGPDframe *gpf, Scene *scene) +{ + int diff; + + if (gpf->flag & GP_FRAME_SELECT) { + diff= CFRA - gpf->framenum; + gpf->framenum= CFRA; + } + + return 0; +} + +static short mirror_gpf_yaxis (bGPDframe *gpf, Scene *scene) +{ + int diff; + + if (gpf->flag & GP_FRAME_SELECT) { + diff= -gpf->framenum; + gpf->framenum= diff; + } + + return 0; +} + +static short mirror_gpf_xaxis (bGPDframe *gpf, Scene *scene) +{ + int diff; + + if (gpf->flag & GP_FRAME_SELECT) { + diff= -gpf->framenum; + gpf->framenum= diff; + } + + return 0; +} + +static short mirror_gpf_marker (bGPDframe *gpf, Scene *scene) +{ + static TimeMarker *marker; + static short initialised = 0; + int diff; + + /* In order for this mirror function to work without + * any extra arguments being added, we use the case + * of bezt==NULL to denote that we should find the + * marker to mirror over. The static pointer is safe + * to use this way, as it will be set to null after + * each cycle in which this is called. + */ + + if (gpf) { + /* mirroring time */ + if ((gpf->flag & GP_FRAME_SELECT) && (marker)) { + diff= (marker->frame - gpf->framenum); + gpf->framenum= (marker->frame + diff); + } + } + else { + /* initialisation time */ + if (initialised) { + /* reset everything for safety */ + marker = NULL; + initialised = 0; + } + else { + /* try to find a marker */ + for (marker= scene->markers.first; marker; marker=marker->next) { + if (marker->flag & SELECT) { + initialised = 1; + break; + } + } + + if (initialised == 0) + marker = NULL; + } + } + + return 0; +} + + +/* mirror selected gp-frames on... */ +void mirror_gplayer_frames (bGPDlayer *gpl, Scene *scene, short mode) +{ + switch (mode) { + case 1: /* mirror over current frame */ + gplayer_frames_looper(gpl, scene, mirror_gpf_cframe); + break; + case 2: /* mirror over frame 0 */ + gplayer_frames_looper(gpl, scene, mirror_gpf_yaxis); + break; + case 3: /* mirror over value 0 */ + gplayer_frames_looper(gpl, scene, mirror_gpf_xaxis); + break; + case 4: /* mirror over marker */ + mirror_gpf_marker(NULL, NULL); + gplayer_frames_looper(gpl, scene, mirror_gpf_marker); + mirror_gpf_marker(NULL, NULL); + break; + default: /* just in case */ + gplayer_frames_looper(gpl, scene, mirror_gpf_yaxis); + break; + } +} + +/* ***************************************** */ diff --git a/source/blender/editors/gpencil/gpencil.c b/source/blender/editors/gpencil/gpencil.c new file mode 100644 index 00000000000..799c9db6d94 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil.c @@ -0,0 +1,2162 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008, Blender Foundation + * This is a new part of Blender + * + * Contributor(s): Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> +#include <math.h> + +#include "MEM_guardedalloc.h" + +#include "BMF_Api.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" + +#include "DNA_listBase.h" +#include "DNA_armature_types.h" +#include "DNA_curve_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" +#include "DNA_vec_types.h" +#include "DNA_view3d_types.h" + +#include "BKE_armature.h" +#include "BKE_blender.h" +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_global.h" +#include "BKE_image.h" +#include "BKE_library.h" +#include "BKE_utildefines.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_view2d.h" + +#include "ED_armature.h" +#include "ED_gpencil.h" +#include "ED_sequencer.h" +#include "ED_view3d.h" + +#include "PIL_time.h" /* sleep */ + +#include "gpencil_intern.h" + +/* XXX */ +static void BIF_undo_push() {} +static void error() {} +static int pupmenu() {return 0;} +static void add_object_draw() {} +static int get_activedevice() {return 0;} +#define L_MOUSE 0 +#define R_MOUSE 0 + +/* XXX */ + +/* ************************************************** */ +/* GENERAL STUFF */ + +/* --------- Memory Management ------------ */ + +/* Free strokes belonging to a gp-frame */ +void free_gpencil_strokes (bGPDframe *gpf) +{ + bGPDstroke *gps, *gpsn; + + /* error checking */ + if (gpf == NULL) return; + + /* free strokes */ + for (gps= gpf->strokes.first; gps; gps= gpsn) { + gpsn= gps->next; + + /* free stroke memory arrays, then stroke itself */ + if (gps->points) MEM_freeN(gps->points); + BLI_freelinkN(&gpf->strokes, gps); + } +} + +/* Free all of a gp-layer's frames */ +void free_gpencil_frames (bGPDlayer *gpl) +{ + bGPDframe *gpf, *gpfn; + + /* error checking */ + if (gpl == NULL) return; + + /* free frames */ + for (gpf= gpl->frames.first; gpf; gpf= gpfn) { + gpfn= gpf->next; + + /* free strokes and their associated memory */ + free_gpencil_strokes(gpf); + BLI_freelinkN(&gpl->frames, gpf); + } +} + +/* Free all of the gp-layers for a viewport (list should be &gpd->layers or so) */ +void free_gpencil_layers (ListBase *list) +{ + bGPDlayer *gpl, *gpln; + + /* error checking */ + if (list == NULL) return; + + /* delete layers*/ + for (gpl= list->first; gpl; gpl= gpln) { + gpln= gpl->next; + + /* free layers and their data */ + free_gpencil_frames(gpl); + BLI_freelinkN(list, gpl); + } +} + +/* Free gp-data and all it's related data */ +void free_gpencil_data (bGPdata *gpd) +{ + /* free layers then data itself */ + free_gpencil_layers(&gpd->layers); + MEM_freeN(gpd); +} + +/* -------- Container Creation ---------- */ + +/* add a new gp-frame to the given layer */ +bGPDframe *gpencil_frame_addnew (bGPDlayer *gpl, int cframe) +{ + bGPDframe *gpf, *gf; + short state=0; + + /* error checking */ + if ((gpl == NULL) || (cframe <= 0)) + return NULL; + + /* allocate memory for this frame */ + gpf= MEM_callocN(sizeof(bGPDframe), "bGPDframe"); + gpf->framenum= cframe; + + /* find appropriate place to add frame */ + if (gpl->frames.first) { + for (gf= gpl->frames.first; gf; gf= gf->next) { + /* check if frame matches one that is supposed to be added */ + if (gf->framenum == cframe) { + state= -1; + break; + } + + /* if current frame has already exceeded the frame to add, add before */ + if (gf->framenum > cframe) { + BLI_insertlinkbefore(&gpl->frames, gf, gpf); + state= 1; + break; + } + } + } + + /* check whether frame was added successfully */ + if (state == -1) { + MEM_freeN(gpf); + printf("Error: frame (%d) existed already for this layer \n", cframe); + } + else if (state == 0) { + /* add to end then! */ + BLI_addtail(&gpl->frames, gpf); + } + + /* return frame */ + return gpf; +} + +/* add a new gp-layer and make it the active layer */ +bGPDlayer *gpencil_layer_addnew (bGPdata *gpd) +{ + bGPDlayer *gpl; + + /* check that list is ok */ + if (gpd == NULL) + return NULL; + + /* allocate memory for frame and add to end of list */ + gpl= MEM_callocN(sizeof(bGPDlayer), "bGPDlayer"); + + /* add to datablock */ + BLI_addtail(&gpd->layers, gpl); + + /* set basic settings */ + gpl->color[3]= 0.9f; + gpl->thickness = 3; + + /* auto-name */ + sprintf(gpl->info, "GP_Layer"); + BLI_uniquename(&gpd->layers, gpl, "GP_Layer", offsetof(bGPDlayer, info[0]), 128); + + /* make this one the active one */ + gpencil_layer_setactive(gpd, gpl); + + /* return layer */ + return gpl; +} + +/* add a new gp-datablock */ +bGPdata *gpencil_data_addnew (void) +{ + bGPdata *gpd; + + /* allocate memory for a new block */ + gpd= MEM_callocN(sizeof(bGPdata), "GreasePencilData"); + + /* initial settings */ + gpd->flag = (GP_DATA_DISPINFO|GP_DATA_EXPAND); + + return gpd; +} + +/* -------- Data Duplication ---------- */ + +/* make a copy of a given gpencil frame */ +bGPDframe *gpencil_frame_duplicate (bGPDframe *src) +{ + bGPDstroke *gps, *gpsd; + bGPDframe *dst; + + /* error checking */ + if (src == NULL) + return NULL; + + /* make a copy of the source frame */ + dst= MEM_dupallocN(src); + dst->prev= dst->next= NULL; + + /* copy strokes */ + dst->strokes.first = dst->strokes.last= NULL; + for (gps= src->strokes.first; gps; gps= gps->next) { + /* make copy of source stroke, then adjust pointer to points too */ + gpsd= MEM_dupallocN(gps); + gpsd->points= MEM_dupallocN(gps->points); + + BLI_addtail(&dst->strokes, gpsd); + } + + /* return new frame */ + return dst; +} + +/* make a copy of a given gpencil layer */ +bGPDlayer *gpencil_layer_duplicate (bGPDlayer *src) +{ + bGPDframe *gpf, *gpfd; + bGPDlayer *dst; + + /* error checking */ + if (src == NULL) + return NULL; + + /* make a copy of source layer */ + dst= MEM_dupallocN(src); + dst->prev= dst->next= NULL; + + /* copy frames */ + dst->frames.first= dst->frames.last= NULL; + for (gpf= src->frames.first; gpf; gpf= gpf->next) { + /* make a copy of source frame */ + gpfd= gpencil_frame_duplicate(gpf); + BLI_addtail(&dst->frames, gpfd); + + /* if source frame was the current layer's 'active' frame, reassign that too */ + if (gpf == dst->actframe) + dst->actframe= gpfd; + } + + /* return new layer */ + return dst; +} + +/* make a copy of a given gpencil datablock */ +bGPdata *gpencil_data_duplicate (bGPdata *src) +{ + bGPDlayer *gpl, *gpld; + bGPdata *dst; + + /* error checking */ + if (src == NULL) + return NULL; + + /* make a copy of the base-data */ + dst= MEM_dupallocN(src); + + /* copy layers */ + dst->layers.first= dst->layers.last= NULL; + for (gpl= src->layers.first; gpl; gpl= gpl->next) { + /* make a copy of source layer and its data */ + gpld= gpencil_layer_duplicate(gpl); + BLI_addtail(&dst->layers, gpld); + } + + /* return new */ + return dst; +} + +/* ----------- GP-Datablock API ------------- */ + +/* get the appropriate bGPdata from the active/given context */ +// XXX region or region data? +bGPdata *gpencil_data_getactive (ScrArea *sa) +{ + ScrArea *curarea= NULL; // XXX + + /* error checking */ + if ((sa == NULL) && (curarea == NULL)) + return NULL; + if (sa == NULL) + sa= curarea; + + /* handle depending on spacetype */ + switch (sa->spacetype) { + case SPACE_VIEW3D: + { + View3D *v3d= sa->spacedata.first; + return v3d->gpd; + } + break; + case SPACE_NODE: + { + SpaceNode *snode= sa->spacedata.first; + return snode->gpd; + } + break; + case SPACE_SEQ: + { + SpaceSeq *sseq= sa->spacedata.first; + + /* only applicable for image modes */ + if (sseq->mainb) + return sseq->gpd; + } + break; + case SPACE_IMAGE: + { + SpaceImage *sima= sa->spacedata.first; + return sima->gpd; + } + break; + } + + /* nothing found */ + return NULL; +} + +/* set bGPdata for the active/given context, and return success/fail */ +short gpencil_data_setactive (ScrArea *sa, bGPdata *gpd) +{ + ScrArea *curarea= NULL; // XXX + + /* error checking */ + if ((sa == NULL) && (curarea == NULL)) + return 0; + if (gpd == NULL) + return 0; + if (sa == NULL) + sa= curarea; + + /* handle depending on spacetype */ + // TODO: someday we should have multi-user data, so no need to loose old data + switch (sa->spacetype) { + case SPACE_VIEW3D: + { + View3D *v3d= sa->spacedata.first; + + /* free the existing block */ + if (v3d->gpd) + free_gpencil_data(v3d->gpd); + v3d->gpd= gpd; + + return 1; + } + break; + case SPACE_NODE: + { + SpaceNode *snode= sa->spacedata.first; + + /* free the existing block */ + if (snode->gpd) + free_gpencil_data(snode->gpd); + snode->gpd= gpd; + + /* set special settings */ + gpd->flag |= GP_DATA_VIEWALIGN; + + return 1; + } + break; + case SPACE_SEQ: + { + SpaceSeq *sseq= sa->spacedata.first; + + /* only applicable if right mode */ + if (sseq->mainb) { + /* free the existing block */ + if (sseq->gpd) + free_gpencil_data(sseq->gpd); + sseq->gpd= gpd; + + return 1; + } + } + break; + case SPACE_IMAGE: + { + SpaceImage *sima= sa->spacedata.first; + + if (sima->gpd) + free_gpencil_data(sima->gpd); + sima->gpd= gpd; + + return 1; + } + break; + } + + /* failed to add */ + return 0; +} + +/* return the ScrArea that has the given GP-datablock + * - assumes that only searching in current screen + * - is based on GP-datablocks only being able to + * exist for one area at a time (i.e. not multiuser) + */ +ScrArea *gpencil_data_findowner (bGPdata *gpd) +{ + bScreen *curscreen= NULL; // XXX + ScrArea *sa; + + /* error checking */ + if (gpd == NULL) + return NULL; + + /* loop over all scrareas for current screen, and check if that area has this gpd */ + for (sa= curscreen->areabase.first; sa; sa= sa->next) { + /* use get-active func to see if match */ + if (gpencil_data_getactive(sa) == gpd) + return sa; + } + + /* not found */ + return NULL; +} + +/* -------- GP-Frame API ---------- */ + +/* delete the last stroke of the given frame */ +void gpencil_frame_delete_laststroke (bGPDframe *gpf) +{ + bGPDstroke *gps= (gpf) ? gpf->strokes.last : NULL; + + /* error checking */ + if (ELEM(NULL, gpf, gps)) + return; + + /* free the stroke and its data */ + MEM_freeN(gps->points); + BLI_freelinkN(&gpf->strokes, gps); +} + +/* -------- GP-Layer API ---------- */ + +/* get the appropriate gp-frame from a given layer + * - this sets the layer's actframe var (if allowed to) + * - extension beyond range (if first gp-frame is after all frame in interest and cannot add) + */ +bGPDframe *gpencil_layer_getframe (bGPDlayer *gpl, int cframe, short addnew) +{ + bGPDframe *gpf = NULL; + short found = 0; + + /* error checking */ + if (gpl == NULL) return NULL; + if (cframe <= 0) cframe = 1; + + /* check if there is already an active frame */ + if (gpl->actframe) { + gpf= gpl->actframe; + + /* do not allow any changes to layer's active frame if layer is locked */ + if (gpl->flag & GP_LAYER_LOCKED) + return gpf; + /* do not allow any changes to actframe if frame has painting tag attached to it */ + if (gpf->flag & GP_FRAME_PAINT) + return gpf; + + /* try to find matching frame */ + if (gpf->framenum < cframe) { + for (; gpf; gpf= gpf->next) { + if (gpf->framenum == cframe) { + found= 1; + break; + } + else if ((gpf->next) && (gpf->next->framenum > cframe)) { + found= 1; + break; + } + } + + /* set the appropriate frame */ + if (addnew) { + if ((found) && (gpf->framenum == cframe)) + gpl->actframe= gpf; + else + gpl->actframe= gpencil_frame_addnew(gpl, cframe); + } + else if (found) + gpl->actframe= gpf; + else + gpl->actframe= gpl->frames.last; + } + else { + for (; gpf; gpf= gpf->prev) { + if (gpf->framenum <= cframe) { + found= 1; + break; + } + } + + /* set the appropriate frame */ + if (addnew) { + if ((found) && (gpf->framenum == cframe)) + gpl->actframe= gpf; + else + gpl->actframe= gpencil_frame_addnew(gpl, cframe); + } + else if (found) + gpl->actframe= gpf; + else + gpl->actframe= gpl->frames.first; + } + } + else if (gpl->frames.first) { + /* check which of the ends to start checking from */ + const int first= ((bGPDframe *)(gpl->frames.first))->framenum; + const int last= ((bGPDframe *)(gpl->frames.last))->framenum; + + if (abs(cframe-first) > abs(cframe-last)) { + /* find gp-frame which is less than or equal to cframe */ + for (gpf= gpl->frames.last; gpf; gpf= gpf->prev) { + if (gpf->framenum <= cframe) { + found= 1; + break; + } + } + } + else { + /* find gp-frame which is less than or equal to cframe */ + for (gpf= gpl->frames.first; gpf; gpf= gpf->next) { + if (gpf->framenum <= cframe) { + found= 1; + break; + } + } + } + + /* set the appropriate frame */ + if (addnew) { + if ((found) && (gpf->framenum == cframe)) + gpl->actframe= gpf; + else + gpl->actframe= gpencil_frame_addnew(gpl, cframe); + } + else if (found) + gpl->actframe= gpf; + else { + /* unresolved errogenous situation! */ + printf("Error: cannot find appropriate gp-frame \n"); + /* gpl->actframe should still be NULL */ + } + } + else { + /* currently no frames (add if allowed to) */ + if (addnew) + gpl->actframe= gpencil_frame_addnew(gpl, cframe); + else { + /* don't do anything... this may be when no frames yet! */ + /* gpl->actframe should still be NULL */ + } + } + + /* return */ + return gpl->actframe; +} + +/* delete the given frame from a layer */ +void gpencil_layer_delframe (bGPDlayer *gpl, bGPDframe *gpf) +{ + /* error checking */ + if (ELEM(NULL, gpl, gpf)) + return; + + /* free the frame and its data */ + free_gpencil_strokes(gpf); + BLI_freelinkN(&gpl->frames, gpf); + gpl->actframe = NULL; +} + +/* get the active gp-layer for editing */ +bGPDlayer *gpencil_layer_getactive (bGPdata *gpd) +{ + bGPDlayer *gpl; + + /* error checking */ + if (ELEM(NULL, gpd, gpd->layers.first)) + return NULL; + + /* loop over layers until found (assume only one active) */ + for (gpl=gpd->layers.first; gpl; gpl=gpl->next) { + if (gpl->flag & GP_LAYER_ACTIVE) + return gpl; + } + + /* no active layer found */ + return NULL; +} + +/* set the active gp-layer */ +void gpencil_layer_setactive (bGPdata *gpd, bGPDlayer *active) +{ + bGPDlayer *gpl; + + /* error checking */ + if (ELEM3(NULL, gpd, gpd->layers.first, active)) + return; + + /* loop over layers deactivating all */ + for (gpl=gpd->layers.first; gpl; gpl=gpl->next) + gpl->flag &= ~GP_LAYER_ACTIVE; + + /* set as active one */ + active->flag |= GP_LAYER_ACTIVE; +} + +/* delete the active gp-layer */ +void gpencil_layer_delactive (bGPdata *gpd) +{ + bGPDlayer *gpl= gpencil_layer_getactive(gpd); + + /* error checking */ + if (ELEM(NULL, gpd, gpl)) + return; + + /* free layer */ + free_gpencil_frames(gpl); + BLI_freelinkN(&gpd->layers, gpl); +} + +/* ************************************************** */ +/* GREASE-PENCIL EDITING - Tools */ + +/* --------- Data Deletion ---------- */ + +/* delete the last stroke on the active layer */ +void gpencil_delete_laststroke (bGPdata *gpd, int cfra) +{ + bGPDlayer *gpl= gpencil_layer_getactive(gpd); + bGPDframe *gpf= gpencil_layer_getframe(gpl, cfra, 0); + + gpencil_frame_delete_laststroke(gpf); +} + +/* delete the active frame */ +void gpencil_delete_actframe (bGPdata *gpd, int cfra) +{ + bGPDlayer *gpl= gpencil_layer_getactive(gpd); + bGPDframe *gpf= gpencil_layer_getframe(gpl, cfra, 0); + + gpencil_layer_delframe(gpl, gpf); +} + + + +/* delete various grase-pencil elements + * mode: 1 - last stroke + * 2 - active frame + * 3 - active layer + */ +void gpencil_delete_operation (int cfra, short mode) +{ + bGPdata *gpd; + + /* get datablock to work on */ + gpd= gpencil_data_getactive(NULL); + if (gpd == NULL) return; + + switch (mode) { + case 1: /* last stroke */ + gpencil_delete_laststroke(gpd, cfra); + break; + case 2: /* active frame */ + gpencil_delete_actframe(gpd, cfra); + break; + case 3: /* active layer */ + gpencil_layer_delactive(gpd); + break; + } + + /* redraw and undo-push */ + BIF_undo_push("GPencil Delete"); +} + +/* display a menu for deleting different grease-pencil elements */ +void gpencil_delete_menu (void) +{ + bGPdata *gpd= gpencil_data_getactive(NULL); + int cfra= 0; // XXX + short mode; + + /* only show menu if it will be relevant */ + if (gpd == NULL) return; + + mode= pupmenu("Grease Pencil Erase...%t|Last Stroke%x1|Active Frame%x2|Active Layer%x3"); + if (mode <= 0) return; + + gpencil_delete_operation(cfra, mode); +} + +/* --------- Data Conversion ---------- */ + +/* convert the coordinates from the given stroke point into 3d-coordinates */ +static void gp_strokepoint_convertcoords (bGPDstroke *gps, bGPDspoint *pt, float p3d[3]) +{ + ARegion *ar= NULL; // XXX + View3D *v3d= NULL; // XXX + + if (gps->flag & GP_STROKE_3DSPACE) { + /* directly use 3d-coordinates */ + VecCopyf(p3d, &pt->x); + } + else { + short mval[2], mx, my; + float *fp= give_cursor(NULL, NULL); // XXX should be scene, v3d + float dvec[3]; + + /* get screen coordinate */ + if (gps->flag & GP_STROKE_2DSPACE) { + // XXX + // View2D *v2d= spacelink_get_view2d(curarea->spacedata.first); + // UI_view2d_view_to_region(v2d, pt->x, pt->y, mval, mval+1); + } + else { + // XXX + // mval[0]= (short)(pt->x / 1000 * curarea->winx); + // mval[1]= (short)(pt->y / 1000 * curarea->winy); + } + mx= mval[0]; + my= mval[1]; + + /* convert screen coordinate to 3d coordinates + * - method taken from editview.c - mouse_cursor() + */ + project_short_noclip(ar, v3d, fp, mval); + window_to_3d(ar, v3d, dvec, mval[0]-mx, mval[1]-my); + VecSubf(p3d, fp, dvec); + } +} + +/* --- */ + +/* convert stroke to 3d path */ +static void gp_stroke_to_path (bGPDlayer *gpl, bGPDstroke *gps, Curve *cu) +{ + bGPDspoint *pt; + Nurb *nu; + BPoint *bp; + int i; + + /* create new 'nurb' within the curve */ + nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_path(nurb)"); + + nu->pntsu= gps->totpoints; + nu->pntsv= 1; + nu->orderu= gps->totpoints; + nu->flagu= 2; /* endpoint */ + nu->resolu= 32; + + nu->bp= (BPoint *)MEM_callocN(sizeof(BPoint)*gps->totpoints, "bpoints"); + + /* add points */ + for (i=0, pt=gps->points, bp=nu->bp; i < gps->totpoints; i++, pt++, bp++) { + float p3d[3]; + + /* get coordinates to add at */ + gp_strokepoint_convertcoords(gps, pt, p3d); + VecCopyf(bp->vec, p3d); + + /* set settings */ + bp->f1= SELECT; + bp->radius = bp->weight = pt->pressure * gpl->thickness; + } + + /* add nurb to curve */ + BLI_addtail(&cu->nurb, nu); +} + +/* convert stroke to 3d bezier */ +static void gp_stroke_to_bezier (bGPDlayer *gpl, bGPDstroke *gps, Curve *cu) +{ + bGPDspoint *pt; + Nurb *nu; + BezTriple *bezt; + int i; + + /* create new 'nurb' within the curve */ + nu = (Nurb *)MEM_callocN(sizeof(Nurb), "gpstroke_to_bezier(nurb)"); + + nu->pntsu= gps->totpoints; + nu->resolu= 12; + nu->resolv= 12; + nu->type= CU_BEZIER; + nu->bezt = (BezTriple *)MEM_callocN(gps->totpoints*sizeof(BezTriple), "bezts"); + + /* add points */ + for (i=0, pt=gps->points, bezt=nu->bezt; i < gps->totpoints; i++, pt++, bezt++) { + float p3d[3]; + + /* get coordinates to add at */ + gp_strokepoint_convertcoords(gps, pt, p3d); + + /* TODO: maybe in future the handles shouldn't be in same place */ + VecCopyf(bezt->vec[0], p3d); + VecCopyf(bezt->vec[1], p3d); + VecCopyf(bezt->vec[2], p3d); + + /* set settings */ + bezt->h1= bezt->h2= HD_FREE; + bezt->f1= bezt->f2= bezt->f3= SELECT; + bezt->radius = bezt->weight = pt->pressure * gpl->thickness * 0.1f; + } + + /* must calculate handles or else we crash */ + calchandlesNurb(nu); + + /* add nurb to curve */ + BLI_addtail(&cu->nurb, nu); +} + +/* convert a given grease-pencil layer to a 3d-curve representation (using current view if appropriate) */ +static void gp_layer_to_curve (bGPdata *gpd, bGPDlayer *gpl, Scene *scene, short mode) +{ + bGPDframe *gpf= gpencil_layer_getframe(gpl, scene->r.cfra, 0); + bGPDstroke *gps; + Object *ob; + Curve *cu; + + /* error checking */ + if (ELEM3(NULL, gpd, gpl, gpf)) + return; + + /* only convert if there are any strokes on this layer's frame to convert */ + if (gpf->strokes.first == NULL) + return; + + /* init the curve object (remove rotation and get curve data from it) + * - must clear transforms set on object, as those skew our results + */ + add_object_draw(OB_CURVE); + ob= OBACT; + ob->loc[0]= ob->loc[1]= ob->loc[2]= 0; + ob->rot[0]= ob->rot[1]= ob->rot[2]= 0; + cu= ob->data; + cu->flag |= CU_3D; + + /* rename object and curve to layer name */ + rename_id((ID *)ob, gpl->info); + rename_id((ID *)cu, gpl->info); + + /* add points to curve */ + for (gps= gpf->strokes.first; gps; gps= gps->next) { + switch (mode) { + case 1: + gp_stroke_to_path(gpl, gps, cu); + break; + case 2: + gp_stroke_to_bezier(gpl, gps, cu); + break; + } + } +} + +/* --- */ + +/* convert a stroke to a bone chain */ +static void gp_stroke_to_bonechain (bGPDlayer *gpl, bGPDstroke *gps, bArmature *arm, ListBase *bones) +{ + EditBone *ebo, *prev=NULL; + bGPDspoint *pt, *ptn; + int i; + + /* add each segment separately */ + for (i=0, pt=gps->points, ptn=gps->points+1; i < (gps->totpoints-1); prev=ebo, i++, pt++, ptn++) { + float p3da[3], p3db[3]; + + /* get coordinates to add at */ + gp_strokepoint_convertcoords(gps, pt, p3da); + gp_strokepoint_convertcoords(gps, ptn, p3db); + + /* allocate new bone */ + ebo= MEM_callocN(sizeof(EditBone), "eBone"); + + VecCopyf(ebo->head, p3da); + VecCopyf(ebo->tail, p3db); + + /* add new bone - note: sync with editarmature.c::add_editbone() */ + { + BLI_strncpy(ebo->name, "Stroke", 32); + unique_editbone_name(bones, ebo->name); + + BLI_addtail(bones, ebo); + + if (i > 0) + { + ebo->flag |= BONE_CONNECTED; + } + ebo->weight= 1.0f; + ebo->dist= 0.25f; + ebo->xwidth= 0.1f; + ebo->zwidth= 0.1f; + ebo->ease1= 1.0f; + ebo->ease2= 1.0f; + ebo->rad_head= pt->pressure * gpl->thickness * 0.1f; + ebo->rad_tail= ptn->pressure * gpl->thickness * 0.1f; + ebo->segments= 1; + ebo->layer= arm->layer; + } + + /* set parenting */ + ebo->parent= prev; + } +} + +/* convert a given grease-pencil layer to a 3d-curve representation (using current view if appropriate) */ +static void gp_layer_to_armature (bGPdata *gpd, bGPDlayer *gpl, Scene *scene, View3D *v3d, short mode) +{ + bGPDframe *gpf= gpencil_layer_getframe(gpl, scene->r.cfra, 0); + bGPDstroke *gps; + Object *ob; + bArmature *arm; + + /* error checking */ + if (ELEM3(NULL, gpd, gpl, gpf)) + return; + + /* only convert if there are any strokes on this layer's frame to convert */ + if (gpf->strokes.first == NULL) + return; + + /* init the armature object (remove rotation and assign armature data to it) + * - must clear transforms set on object, as those skew our results + */ + add_object_draw(OB_ARMATURE); + ob= OBACT; + ob->loc[0]= ob->loc[1]= ob->loc[2]= 0; + ob->rot[0]= ob->rot[1]= ob->rot[2]= 0; + arm= ob->data; + + /* rename object and armature to layer name */ + rename_id((ID *)ob, gpl->info); + rename_id((ID *)arm, gpl->info); + + /* this is editmode armature */ + arm->edbo= MEM_callocN(sizeof(ListBase), "arm edbo"); + + /* convert segments to bones, strokes to bone chains */ + for (gps= gpf->strokes.first; gps; gps= gps->next) { + gp_stroke_to_bonechain(gpl, gps, arm, arm->edbo); + } + + /* adjust roll of bones + * - set object as EditMode object, but need to clear afterwards! + * - use 'align to world z-up' option + */ + { + /* set our data as if we're in editmode to fool auto_align_armature() */ + scene->obedit= ob; + + /* WARNING: need to make sure this magic number doesn't change */ + auto_align_armature(scene, v3d, 2); + + scene->obedit= NULL; + } + + /* flush editbones to armature */ + ED_armature_from_edit(scene, ob); + ED_armature_edit_free(ob); +} + +/* --- */ + +/* convert grease-pencil strokes to another representation + * mode: 1 - Active layer to path + * 2 - Active layer to bezier + * 3 - Active layer to armature + */ +void gpencil_convert_operation (short mode) +{ + Scene *scene= NULL; // XXX + View3D *v3d= NULL; // XXX + bGPdata *gpd; + float *fp= give_cursor(scene, v3d); + + /* get datablock to work on */ + gpd= gpencil_data_getactive(NULL); + if (gpd == NULL) return; + + /* initialise 3d-cursor correction globals */ + initgrabz(v3d, fp[0], fp[1], fp[2]); + + /* handle selection modes */ + switch (mode) { + case 1: /* active layer only (to path) */ + case 2: /* active layer only (to bezier) */ + { + bGPDlayer *gpl= gpencil_layer_getactive(gpd); + gp_layer_to_curve(gpd, gpl, scene, mode); + } + break; + case 3: /* active layer only (to armature) */ + { + bGPDlayer *gpl= gpencil_layer_getactive(gpd); + gp_layer_to_armature(gpd, gpl, scene, v3d, mode); + } + break; + } + + /* redraw and undo-push */ + BIF_undo_push("GPencil Convert"); +} + +/* display a menu for converting grease-pencil strokes */ +void gpencil_convert_menu (void) +{ + bGPdata *gpd= gpencil_data_getactive(NULL); + short mode; + + /* only show menu if it will be relevant */ + if (gpd == NULL) return; + + mode= pupmenu("Grease Pencil Convert %t|Active Layer To Path%x1|Active Layer to Bezier%x2|Active Layer to Armature%x3"); + if (mode <= 0) return; + + gpencil_convert_operation(mode); +} + +/* ************************************************** */ +/* GREASE-PENCIL EDITING MODE - Painting */ + +/* ---------- 'Globals' and Defines ----------------- */ + +/* maximum sizes of gp-session buffer */ +#define GP_STROKE_BUFFER_MAX 5000 + +/* Macros for accessing sensitivity thresholds... */ + /* minimum number of pixels mouse should move before new point created */ +#define MIN_MANHATTEN_PX (U.gp_manhattendist) + /* minimum length of new segment before new point can be added */ +#define MIN_EUCLIDEAN_PX (U.gp_euclideandist) + +/* macro to test if only converting endpoints - only for use when converting! */ +#define GP_BUFFER2STROKE_ENDPOINTS ((gpd->flag & GP_DATA_EDITPAINT) && (ctrl)) + +/* ------ */ + +/* Temporary 'Stroke' Operation data */ +typedef struct tGPsdata { + Scene *scene; /* current scene from context */ + ScrArea *sa; /* area where painting originated */ + ARegion *ar; /* region where painting originated */ + View2D *v2d; /* needed for GP_STROKE_2DSPACE */ + + ImBuf *ibuf; /* needed for GP_STROKE_2DIMAGE */ + struct IBufViewSettings { + int offsx, offsy; /* offsets */ + int sizex, sizey; /* dimensions to use as scale-factor */ + } im2d_settings; /* needed for GP_STROKE_2DIMAGE */ + + bGPdata *gpd; /* gp-datablock layer comes from */ + bGPDlayer *gpl; /* layer we're working on */ + bGPDframe *gpf; /* frame we're working on */ + + short status; /* current status of painting */ + short paintmode; /* mode for painting */ + + short mval[2]; /* current mouse-position */ + short mvalo[2]; /* previous recorded mouse-position */ + + float pressure; /* current stylus pressure */ + float opressure; /* previous stylus pressure */ + + short radius; /* radius of influence for eraser */ +} tGPsdata; + +/* values for tGPsdata->status */ +enum { + GP_STATUS_NORMAL = 0, /* running normally */ + GP_STATUS_ERROR, /* something wasn't correctly set up */ + GP_STATUS_DONE /* painting done */ +}; + +/* values for tGPsdata->paintmode */ +enum { + GP_PAINTMODE_DRAW = 0, + GP_PAINTMODE_ERASER +}; + +/* Return flags for adding points to stroke buffer */ +enum { + GP_STROKEADD_INVALID = -2, /* error occurred - insufficient info to do so */ + GP_STROKEADD_OVERFLOW = -1, /* error occurred - cannot fit any more points */ + GP_STROKEADD_NORMAL, /* point was successfully added */ + GP_STROKEADD_FULL /* cannot add any more points to buffer */ +}; + +/* ---------- Stroke Editing ------------ */ + +/* clear the session buffers (call this before AND after a paint operation) */ +static void gp_session_validatebuffer (tGPsdata *p) +{ + bGPdata *gpd= p->gpd; + + /* clear memory of buffer (or allocate it if starting a new session) */ + if (gpd->sbuffer) + memset(gpd->sbuffer, 0, sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX); + else + gpd->sbuffer= MEM_callocN(sizeof(tGPspoint)*GP_STROKE_BUFFER_MAX, "gp_session_strokebuffer"); + + /* reset indices */ + gpd->sbuffer_size = 0; + + /* reset flags */ + gpd->sbuffer_sflag= 0; +} + +/* check if the current mouse position is suitable for adding a new point */ +static short gp_stroke_filtermval (tGPsdata *p, short mval[2], short pmval[2]) +{ + short dx= abs(mval[0] - pmval[0]); + short dy= abs(mval[1] - pmval[1]); + + /* check if mouse moved at least certain distance on both axes (best case) */ + if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX)) + return 1; + + /* check if the distance since the last point is significant enough */ + // future optimisation: sqrt here may be too slow? + else if (sqrt(dx*dx + dy*dy) > MIN_EUCLIDEAN_PX) + return 1; + + /* mouse 'didn't move' */ + else + return 0; +} + +/* convert screen-coordinates to buffer-coordinates */ +static void gp_stroke_convertcoords (tGPsdata *p, short mval[], float out[]) +{ + View3D *v3d= NULL; // XXX + bGPdata *gpd= p->gpd; + + /* in 3d-space - pt->x/y/z are 3 side-by-side floats */ + if (gpd->sbuffer_sflag & GP_STROKE_3DSPACE) { + const short mx=mval[0], my=mval[1]; + float *fp= give_cursor(p->scene, NULL); // XXX NULL could be v3d + float dvec[3]; + + /* Current method just converts each point in screen-coordinates to + * 3D-coordinates using the 3D-cursor as reference. In general, this + * works OK, but it could of course be improved. + * + * TODO: + * - investigate using nearest point(s) on a previous stroke as + * reference point instead or as offset, for easier stroke matching + * - investigate projection onto geometry (ala retopo) + */ + + /* method taken from editview.c - mouse_cursor() */ + project_short_noclip(p->ar, v3d, fp, mval); + window_to_3d(p->ar, v3d, dvec, mval[0]-mx, mval[1]-my); + VecSubf(out, fp, dvec); + } + + /* 2d - on 'canvas' (assume that p->v2d is set) */ + else if ((gpd->sbuffer_sflag & GP_STROKE_2DSPACE) && (p->v2d)) { + float x, y; + + UI_view2d_region_to_view(p->v2d, mval[0], mval[1], &x, &y); + + out[0]= x; + out[1]= y; + } + + /* 2d - on image 'canvas' (assume that p->v2d is set) */ + else if (gpd->sbuffer_sflag & GP_STROKE_2DIMAGE) { + int sizex, sizey, offsx, offsy; + + /* get stored settings + * - assume that these have been set already (there are checks that set sane 'defaults' just in case) + */ + sizex= p->im2d_settings.sizex; + sizey= p->im2d_settings.sizey; + offsx= p->im2d_settings.offsx; + offsy= p->im2d_settings.offsy; + + /* calculate new points */ + out[0]= (float)(mval[0] - offsx) / (float)sizex; + out[1]= (float)(mval[1] - offsy) / (float)sizey; + } + + /* 2d - relative to screen (viewport area) */ + else { + out[0] = (float)(mval[0]) / (float)(p->sa->winx) * 1000; + out[1] = (float)(mval[1]) / (float)(p->sa->winy) * 1000; + } +} + +/* add current stroke-point to buffer (returns whether point was successfully added) */ +static short gp_stroke_addpoint (tGPsdata *p, short mval[2], float pressure) +{ + bGPdata *gpd= p->gpd; + tGPspoint *pt; + + /* check if still room in buffer */ + if (gpd->sbuffer_size >= GP_STROKE_BUFFER_MAX) + return GP_STROKEADD_OVERFLOW; + + /* get pointer to destination point */ + pt= ((tGPspoint *)(gpd->sbuffer) + gpd->sbuffer_size); + + /* store settings */ + pt->x= mval[0]; + pt->y= mval[1]; + pt->pressure= pressure; + + /* increment counters */ + gpd->sbuffer_size++; + + /* check if another operation can still occur */ + if (gpd->sbuffer_size == GP_STROKE_BUFFER_MAX) + return GP_STROKEADD_FULL; + else + return GP_STROKEADD_NORMAL; +} + +/* smooth a stroke (in buffer) before storing it */ +static void gp_stroke_smooth (tGPsdata *p) +{ + bGPdata *gpd= p->gpd; + int i=0, cmx=gpd->sbuffer_size; + int ctrl= 0; // XXX + + /* only smooth if smoothing is enabled, and we're not doing a straight line */ + if (!(U.gp_settings & GP_PAINT_DOSMOOTH) || GP_BUFFER2STROKE_ENDPOINTS) + return; + + /* don't try if less than 2 points in buffer */ + if ((cmx <= 2) || (gpd->sbuffer == NULL)) + return; + + /* apply weighting-average (note doing this along path sequentially does introduce slight error) */ + for (i=0; i < gpd->sbuffer_size; i++) { + tGPspoint *pc= (((tGPspoint *)gpd->sbuffer) + i); + tGPspoint *pb= (i-1 > 0)?(pc-1):(pc); + tGPspoint *pa= (i-2 > 0)?(pc-2):(pb); + tGPspoint *pd= (i+1 < cmx)?(pc+1):(pc); + tGPspoint *pe= (i+2 < cmx)?(pc+2):(pd); + + pc->x= (short)(0.1*pa->x + 0.2*pb->x + 0.4*pc->x + 0.2*pd->x + 0.1*pe->x); + pc->y= (short)(0.1*pa->y + 0.2*pb->y + 0.4*pc->y + 0.2*pd->y + 0.1*pe->y); + } +} + +/* simplify a stroke (in buffer) before storing it + * - applies a reverse Chaikin filter + * - code adapted from etch-a-ton branch (editarmature_sketch.c) + */ +static void gp_stroke_simplify (tGPsdata *p) +{ + bGPdata *gpd= p->gpd; + tGPspoint *old_points= (tGPspoint *)gpd->sbuffer; + short num_points= gpd->sbuffer_size; + short flag= gpd->sbuffer_sflag; + short i, j; + int ctrl= 0; // XXX + + /* only simplify if simlification is enabled, and we're not doing a straight line */ + if (!(U.gp_settings & GP_PAINT_DOSIMPLIFY) || GP_BUFFER2STROKE_ENDPOINTS) + return; + + /* don't simplify if less than 4 points in buffer */ + if ((num_points <= 2) || (old_points == NULL)) + return; + + /* 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 + * - secondly, reset flag after, as it gets cleared auto + */ + gpd->sbuffer= NULL; + gp_session_validatebuffer(p); + gpd->sbuffer_sflag = flag; + +/* macro used in loop to get position of new point + * - used due to the mixture of datatypes in use here + */ +#define GP_SIMPLIFY_AVPOINT(offs, sfac) \ + { \ + co[0] += (float)(old_points[offs].x * sfac); \ + co[1] += (float)(old_points[offs].y * sfac); \ + pressure += old_points[offs].pressure * sfac; \ + } + + for (i = 0, j = 0; i < num_points; i++) + { + if (i - j == 3) + { + float co[2], pressure; + short mco[2]; + + /* initialise values */ + co[0]= 0; + co[1]= 0; + pressure = 0; + + /* using macro, calculate new point */ + GP_SIMPLIFY_AVPOINT(j, -0.25f); + GP_SIMPLIFY_AVPOINT(j+1, 0.75f); + GP_SIMPLIFY_AVPOINT(j+2, 0.75f); + GP_SIMPLIFY_AVPOINT(j+3, -0.25f); + + /* set values for adding */ + mco[0]= (short)co[0]; + mco[1]= (short)co[1]; + + /* ignore return values on this... assume to be ok for now */ + gp_stroke_addpoint(p, mco, pressure); + + j += 2; + } + } + + /* free old buffer */ + MEM_freeN(old_points); +} + + +/* make a new stroke from the buffer data */ +static void gp_stroke_newfrombuffer (tGPsdata *p) +{ + bGPdata *gpd= p->gpd; + bGPDstroke *gps; + bGPDspoint *pt; + tGPspoint *ptc; + int i, totelem; + int ctrl= 0; // XXX + + /* get total number of points to allocate space for: + * - in 'Draw Mode', holding the Ctrl-Modifier will only take endpoints + * - otherwise, do whole stroke + */ + if (GP_BUFFER2STROKE_ENDPOINTS) + totelem = (gpd->sbuffer_size >= 2) ? 2: gpd->sbuffer_size; + else + totelem = gpd->sbuffer_size; + + /* exit with error if no valid points from this stroke */ + if (totelem == 0) { + if (G.f & G_DEBUG) + printf("Error: No valid points in stroke buffer to convert (tot=%d) \n", gpd->sbuffer_size); + return; + } + + /* allocate memory for a new stroke */ + gps= MEM_callocN(sizeof(bGPDstroke), "gp_stroke"); + + /* allocate enough memory for a continuous array for storage points */ + pt= gps->points= MEM_callocN(sizeof(bGPDspoint)*totelem, "gp_stroke_points"); + + /* copy appropriate settings for stroke */ + gps->totpoints= totelem; + gps->thickness= p->gpl->thickness; + gps->flag= gpd->sbuffer_sflag; + + /* copy points from the buffer to the stroke */ + if (GP_BUFFER2STROKE_ENDPOINTS) { + /* 'Draw Mode' + Ctrl-Modifier - only endpoints */ + { + /* first point */ + ptc= gpd->sbuffer; + + /* convert screen-coordinates to appropriate coordinates (and store them) */ + gp_stroke_convertcoords(p, &ptc->x, &pt->x); + + /* copy pressure */ + pt->pressure= ptc->pressure; + + pt++; + } + + if (totelem == 2) { + /* last point if applicable */ + ptc= ((tGPspoint *)gpd->sbuffer) + (gpd->sbuffer_size - 1); + + /* convert screen-coordinates to appropriate coordinates (and store them) */ + gp_stroke_convertcoords(p, &ptc->x, &pt->x); + + /* copy pressure */ + pt->pressure= ptc->pressure; + } + } + else { + /* convert all points (normal behaviour) */ + for (i=0, ptc=gpd->sbuffer; i < gpd->sbuffer_size && ptc; i++, ptc++) { + /* convert screen-coordinates to appropriate coordinates (and store them) */ + gp_stroke_convertcoords(p, &ptc->x, &pt->x); + + /* copy pressure */ + pt->pressure= ptc->pressure; + + pt++; + } + } + + /* add stroke to frame */ + BLI_addtail(&p->gpf->strokes, gps); +} + +/* --- 'Eraser' for 'Paint' Tool ------ */ + +/* eraser tool - remove segment from stroke/split stroke (after lasso inside) */ +static short gp_stroke_eraser_splitdel (bGPDframe *gpf, bGPDstroke *gps, int i) +{ + bGPDspoint *pt_tmp= gps->points; + bGPDstroke *gsn = NULL; + + /* if stroke only had two points, get rid of stroke */ + if (gps->totpoints == 2) { + /* free stroke points, then stroke */ + MEM_freeN(pt_tmp); + BLI_freelinkN(&gpf->strokes, gps); + + /* nothing left in stroke, so stop */ + return 1; + } + + /* if last segment, just remove segment from the stroke */ + else if (i == gps->totpoints - 2) { + /* allocate new points array, and assign most of the old stroke there */ + gps->totpoints--; + gps->points= MEM_callocN(sizeof(bGPDspoint)*gps->totpoints, "gp_stroke_points"); + memcpy(gps->points, pt_tmp, sizeof(bGPDspoint)*gps->totpoints); + + /* free temp buffer */ + MEM_freeN(pt_tmp); + + /* nothing left in stroke, so stop */ + return 1; + } + + /* if first segment, just remove segment from the stroke */ + else if (i == 0) { + /* allocate new points array, and assign most of the old stroke there */ + gps->totpoints--; + gps->points= MEM_callocN(sizeof(bGPDspoint)*gps->totpoints, "gp_stroke_points"); + memcpy(gps->points, pt_tmp + 1, sizeof(bGPDspoint)*gps->totpoints); + + /* free temp buffer */ + MEM_freeN(pt_tmp); + + /* no break here, as there might still be stuff to remove in this stroke */ + return 0; + } + + /* segment occurs in 'middle' of stroke, so split */ + else { + /* duplicate stroke, and assign 'later' data to that stroke */ + gsn= MEM_dupallocN(gps); + gsn->prev= gsn->next= NULL; + BLI_insertlinkafter(&gpf->strokes, gps, gsn); + + gsn->totpoints= gps->totpoints - i; + gsn->points= MEM_callocN(sizeof(bGPDspoint)*gsn->totpoints, "gp_stroke_points"); + memcpy(gsn->points, pt_tmp + i, sizeof(bGPDspoint)*gsn->totpoints); + + /* adjust existing stroke */ + gps->totpoints= i; + gps->points= MEM_callocN(sizeof(bGPDspoint)*gps->totpoints, "gp_stroke_points"); + memcpy(gps->points, pt_tmp, sizeof(bGPDspoint)*i); + + /* free temp buffer */ + MEM_freeN(pt_tmp); + + /* nothing left in stroke, so stop */ + return 1; + } +} + +/* eraser tool - check if part of stroke occurs within last segment drawn by eraser */ +static short gp_stroke_eraser_strokeinside (short mval[], short mvalo[], short rad, short x0, short y0, short x1, short y1) +{ + /* simple within-radius check for now */ + if (edge_inside_circle(mval[0], mval[1], rad, x0, y0, x1, y1)) + return 1; + + /* not inside */ + return 0; +} + +/* eraser tool - evaluation per stroke */ +static void gp_stroke_eraser_dostroke (tGPsdata *p, short mval[], short mvalo[], short rad, rcti *rect, bGPDframe *gpf, bGPDstroke *gps) +{ + bGPDspoint *pt1, *pt2; + View3D *v3d= NULL; + short x0=0, y0=0, x1=0, y1=0; + short xyval[2]; + int i; + + if (gps->totpoints == 0) { + /* just free stroke */ + if (gps->points) + MEM_freeN(gps->points); + BLI_freelinkN(&gpf->strokes, gps); + } + else if (gps->totpoints == 1) { + /* get coordinates */ + if (gps->flag & GP_STROKE_3DSPACE) { + project_short(p->ar, v3d, &gps->points->x, xyval); + x0= xyval[0]; + y0= xyval[1]; + } + else if (gps->flag & GP_STROKE_2DSPACE) { + UI_view2d_view_to_region(p->v2d, gps->points->x, gps->points->y, &x0, &y0); + } + else if (gps->flag & GP_STROKE_2DIMAGE) { + int offsx, offsy, sizex, sizey; + + /* get stored settings */ + sizex= p->im2d_settings.sizex; + sizey= p->im2d_settings.sizey; + offsx= p->im2d_settings.offsx; + offsy= p->im2d_settings.offsy; + + /* calculate new points */ + x0= (short)((gps->points->x * sizex) + offsx); + y0= (short)((gps->points->y * sizey) + offsy); + } + else { + x0= (short)(gps->points->x / 1000 * p->sa->winx); + y0= (short)(gps->points->y / 1000 * p->sa->winy); + } + + /* do boundbox check first */ + if (BLI_in_rcti(rect, x0, y0)) { + /* only check if point is inside */ + if ( ((x0-mval[0])*(x0-mval[0]) + (y0-mval[1])*(y0-mval[1])) <= rad*rad ) { + /* free stroke */ + MEM_freeN(gps->points); + BLI_freelinkN(&gpf->strokes, gps); + } + } + } + else { + /* loop over the points in the stroke, checking for intersections + * - an intersection will require the stroke to be split + */ + for (i=0; (i+1) < gps->totpoints; i++) { + /* get points to work with */ + pt1= gps->points + i; + pt2= gps->points + i + 1; + + /* get coordinates */ + if (gps->flag & GP_STROKE_3DSPACE) { + project_short(p->ar, v3d, &pt1->x, xyval); + x0= xyval[0]; + y0= xyval[1]; + + project_short(p->ar, v3d, &pt2->x, xyval); + x1= xyval[0]; + y1= xyval[1]; + } + else if (gps->flag & GP_STROKE_2DSPACE) { + UI_view2d_view_to_region(p->v2d, pt1->x, pt1->y, &x0, &y0); + + UI_view2d_view_to_region(p->v2d, pt2->x, pt2->y, &x1, &y1); + } + else if (gps->flag & GP_STROKE_2DIMAGE) { + int offsx, offsy, sizex, sizey; + + /* get stored settings */ + sizex= p->im2d_settings.sizex; + sizey= p->im2d_settings.sizey; + offsx= p->im2d_settings.offsx; + offsy= p->im2d_settings.offsy; + + /* calculate new points */ + x0= (short)((pt1->x * sizex) + offsx); + y0= (short)((pt1->y * sizey) + offsy); + + x1= (short)((pt2->x * sizex) + offsx); + y1= (short)((pt2->y * sizey) + offsy); + } + else { + x0= (short)(pt1->x / 1000 * p->sa->winx); + y0= (short)(pt1->y / 1000 * p->sa->winy); + x1= (short)(pt2->x / 1000 * p->sa->winx); + y1= (short)(pt2->y / 1000 * p->sa->winy); + } + + /* check that point segment of the boundbox of the eraser stroke */ + if (BLI_in_rcti(rect, x0, y0) || BLI_in_rcti(rect, x1, y1)) { + /* check if point segment of stroke had anything to do with + * eraser region (either within stroke painted, or on its lines) + * - this assumes that linewidth is irrelevant + */ + if (gp_stroke_eraser_strokeinside(mval, mvalo, rad, x0, y0, x1, y1)) { + /* if function returns true, break this loop (as no more point to check) */ + if (gp_stroke_eraser_splitdel(gpf, gps, i)) + break; + } + } + } + } +} + +/* erase strokes which fall under the eraser strokes */ +static void gp_stroke_doeraser (tGPsdata *p) +{ + bGPDframe *gpf= p->gpf; + bGPDstroke *gps, *gpn; + rcti rect; + + /* rect is rectangle of eraser */ + rect.xmin= p->mval[0] - p->radius; + rect.ymin= p->mval[1] - p->radius; + rect.xmax= p->mval[0] + p->radius; + rect.ymax= p->mval[1] + p->radius; + + /* loop over strokes, checking segments for intersections */ + for (gps= gpf->strokes.first; gps; gps= gpn) { + gpn= gps->next; + gp_stroke_eraser_dostroke(p, p->mval, p->mvalo, p->radius, &rect, gpf, gps); + } +} + +/* ---------- 'Paint' Tool ------------ */ + +/* init new painting session */ +static void gp_session_initpaint (bContext *C, tGPsdata *p) +{ + ScrArea *curarea= CTX_wm_area(C); + ARegion *ar= CTX_wm_region(C); + + /* clear previous data (note: is on stack) */ + memset(p, 0, sizeof(tGPsdata)); + + /* make sure the active view (at the starting time) is a 3d-view */ + if (curarea == NULL) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: No active view for painting \n"); + return; + } + + /* pass on current scene */ + p->scene= CTX_data_scene(C); + + switch (curarea->spacetype) { + /* supported views first */ + case SPACE_VIEW3D: + { + View3D *v3d= curarea->spacedata.first; + + /* set current area */ + p->sa= curarea; + p->ar= ar; + + /* check that gpencil data is allowed to be drawn */ + if ((v3d->flag2 & V3D_DISPGP)==0) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: In active view, Grease Pencil not shown \n"); + return; + } + } + break; + case SPACE_NODE: + { + SpaceNode *snode= curarea->spacedata.first; + + /* set current area */ + p->sa= curarea; + p->ar= ar; + p->v2d= &ar->v2d; + + /* check that gpencil data is allowed to be drawn */ + if ((snode->flag & SNODE_DISPGP)==0) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: In active view, Grease Pencil not shown \n"); + return; + } + } + break; + case SPACE_SEQ: + { + SpaceSeq *sseq= curarea->spacedata.first; + + /* set current area */ + p->sa= curarea; + p->ar= ar; + p->v2d= &ar->v2d; + + /* check that gpencil data is allowed to be drawn */ + if (sseq->mainb == 0) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: In active view (sequencer), active mode doesn't support Grease Pencil \n"); + return; + } + if ((sseq->flag & SEQ_DRAW_GPENCIL)==0) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: In active view, Grease Pencil not shown \n"); + return; + } + } + break; + case SPACE_IMAGE: + { + SpaceImage *sima= curarea->spacedata.first; + + /* set the current area */ + p->sa= curarea; + p->ar= ar; + p->v2d= &ar->v2d; + p->ibuf= BKE_image_get_ibuf(sima->image, &sima->iuser); + + /* check that gpencil data is allowed to be drawn */ + if ((sima->flag & SI_DISPGP)==0) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: In active view, Grease Pencil not shown \n"); + return; + } + } + break; + /* unsupported views */ + default: + { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: Active view not appropriate for Grease Pencil drawing \n"); + return; + } + break; + } + + /* get gp-data */ + p->gpd= gpencil_data_getactive(p->sa); + if (p->gpd == NULL) { + short ok; + + p->gpd= gpencil_data_addnew(); + ok= gpencil_data_setactive(p->sa, p->gpd); + + /* most of the time, the following check isn't needed */ + if (ok == 0) { + /* free gpencil data as it can't be used */ + free_gpencil_data(p->gpd); + p->gpd= NULL; + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: Could not assign newly created Grease Pencil data to active area \n"); + return; + } + } + + /* set edit flags */ + G.f |= G_GREASEPENCIL; + + /* clear out buffer (stored in gp-data) in case something contaminated it */ + gp_session_validatebuffer(p); + + /* set 'default' im2d_settings just in case something that uses this doesn't set it */ + p->im2d_settings.sizex= 1; + p->im2d_settings.sizey= 1; +} + +/* cleanup after a painting session */ +static void gp_session_cleanup (tGPsdata *p) +{ + bGPdata *gpd= p->gpd; + + /* error checking */ + if (gpd == NULL) + return; + + /* free stroke buffer */ + if (gpd->sbuffer) { + MEM_freeN(gpd->sbuffer); + gpd->sbuffer= NULL; + } + + /* clear flags */ + gpd->sbuffer_size= 0; + gpd->sbuffer_sflag= 0; +} + +/* init new stroke */ +static void gp_paint_initstroke (tGPsdata *p, short paintmode) +{ + /* get active layer (or add a new one if non-existent) */ + p->gpl= gpencil_layer_getactive(p->gpd); + if (p->gpl == NULL) + p->gpl= gpencil_layer_addnew(p->gpd); + if (p->gpl->flag & GP_LAYER_LOCKED) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: Cannot paint on locked layer \n"); + return; + } + + /* get active frame (add a new one if not matching frame) */ + p->gpf= gpencil_layer_getframe(p->gpl, p->scene->r.cfra, 1); + if (p->gpf == NULL) { + p->status= GP_STATUS_ERROR; + if (G.f & G_DEBUG) + printf("Error: No frame created (gpencil_paint_init) \n"); + return; + } + else + p->gpf->flag |= GP_FRAME_PAINT; + + /* set 'eraser' for this stroke if using eraser */ + p->paintmode= paintmode; + if (p->paintmode == GP_PAINTMODE_ERASER) + p->gpd->sbuffer_sflag |= GP_STROKE_ERASER; + + /* check if points will need to be made in view-aligned space */ + if (p->gpd->flag & GP_DATA_VIEWALIGN) { + switch (p->sa->spacetype) { + case SPACE_VIEW3D: + { + View3D *v3d= (View3D *)p->sa->spacedata.first; + float *fp= give_cursor(p->scene, v3d); + initgrabz(v3d, fp[0], fp[1], fp[2]); + + p->gpd->sbuffer_sflag |= GP_STROKE_3DSPACE; + } + break; + case SPACE_NODE: + { + p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; + } + break; + case SPACE_SEQ: + { + SpaceSeq *sseq= (SpaceSeq *)p->sa->spacedata.first; + int rectx, recty; + float zoom, zoomx, zoomy; + + /* set draw 2d-stroke flag */ + p->gpd->sbuffer_sflag |= GP_STROKE_2DIMAGE; + + /* calculate zoom factor */ + zoom= (float)(SEQ_ZOOM_FAC(sseq->zoom)); + if (sseq->mainb == SEQ_DRAW_IMG_IMBUF) { + zoomx = zoom * ((float)p->scene->r.xasp / (float)p->scene->r.yasp); + zoomy = zoom; + } + else + zoomx = zoomy = zoom; + + /* calculate rect size to use to calculate the size of the drawing area + * - We use the size of the output image not the size of the ibuf being shown + * as it is too messy getting the ibuf (and could be too slow). This should be + * a reasonable for most cases anyway. + */ + rectx= (p->scene->r.size * p->scene->r.xsch) / 100; + recty= (p->scene->r.size * p->scene->r.ysch) / 100; + + /* set offset and scale values for opertations to use */ + p->im2d_settings.sizex= (int)(zoomx * rectx); + p->im2d_settings.sizey= (int)(zoomy * recty); + p->im2d_settings.offsx= (int)((p->sa->winx-p->im2d_settings.sizex)/2 + sseq->xof); + p->im2d_settings.offsy= (int)((p->sa->winy-p->im2d_settings.sizey)/2 + sseq->yof); + } + break; + case SPACE_IMAGE: + { + /* check if any ibuf available */ + if (p->ibuf) + p->gpd->sbuffer_sflag |= GP_STROKE_2DSPACE; + } + break; + } + } +} + +/* finish off a stroke (clears buffer, but doesn't finish the paint operation) */ +static void gp_paint_strokeend (tGPsdata *p) +{ + /* check if doing eraser or not */ + if ((p->gpd->sbuffer_sflag & GP_STROKE_ERASER) == 0) { + /* smooth stroke before transferring? */ + gp_stroke_smooth(p); + + /* simplify stroke before transferring? */ + gp_stroke_simplify(p); + + /* transfer stroke to frame */ + gp_stroke_newfrombuffer(p); + } + + /* clean up buffer now */ + gp_session_validatebuffer(p); +} + +/* finish off stroke painting operation */ +static void gp_paint_cleanup (tGPsdata *p) +{ + /* finish off a stroke */ + gp_paint_strokeend(p); + + /* "unlock" frame */ + p->gpf->flag &= ~GP_FRAME_PAINT; + + /* add undo-push so stroke can be undone */ + /* FIXME: currently disabled, as it's impossible to get this working nice + * as gpenci data is on currently screen-level (which isn't saved to undo files) + */ + //BIF_undo_push("GPencil Stroke"); + + /* force redraw after drawing action */ + // XXX force_draw_plus(SPACE_ACTION, 0); +} + +/* -------- */ + +/* main call to paint a new stroke */ +// XXX will become modal(), gets event, includes all info! +short gpencil_paint (bContext *C, short paintmode) +{ + tGPsdata p; + short ok = GP_STROKEADD_NORMAL; + + /* init paint-data */ + gp_session_initpaint(C, &p); + if (p.status == GP_STATUS_ERROR) { + gp_session_cleanup(&p); + return 0; + } + gp_paint_initstroke(&p, paintmode); + if (p.status == GP_STATUS_ERROR) { + gp_session_cleanup(&p); + return 0; + } + + /* set cursor to indicate drawing */ + // XXX (cursor callbacks in regiontype) setcursor_space(p.sa->spacetype, CURSOR_VPAINT); + + /* init drawing-device settings */ + // XXX getmouseco_areawin(p.mval); + // XXX p.pressure = get_pressure(); + + p.mvalo[0]= p.mval[0]; + p.mvalo[1]= p.mval[1]; + p.opressure= p.pressure; + + /* radius for eraser circle is defined in userprefs now */ + // TODO: make this more easily tweaked... + p.radius= U.gp_eraser; + + /* start drawing eraser-circle (if applicable) */ + //if (paintmode == GP_PAINTMODE_ERASER) + // XXX draw_sel_circle(p.mval, NULL, p.radius, p.radius, 0); // draws frontbuffer, but sets backbuf again + + /* only allow painting of single 'dots' if: + * - pressure is not excessive (as it can be on some windows tablets) + * - draw-mode for active datablock is turned on + * - not erasing + */ + if (paintmode != GP_PAINTMODE_ERASER) { + if (!(p.pressure >= 0.99f) || (p.gpd->flag & GP_DATA_EDITPAINT)) { + gp_stroke_addpoint(&p, p.mval, p.pressure); + } + } + + /* XXX paint loop */ + if(0) { + /* get current user input */ + // XXX getmouseco_areawin(p.mval); + // XXX p.pressure = get_pressure(); + + /* only add current point to buffer if mouse moved (otherwise wait until it does) */ + if (paintmode == GP_PAINTMODE_ERASER) { + /* do 'live' erasing now */ + gp_stroke_doeraser(&p); + + // XXX draw_sel_circle(p.mval, p.mvalo, p.radius, p.radius, 0); + // XXX force_draw(0); + + p.mvalo[0]= p.mval[0]; + p.mvalo[1]= p.mval[1]; + p.opressure= p.pressure; + } + else if (gp_stroke_filtermval(&p, p.mval, p.mvalo)) { + /* try to add point */ + ok= gp_stroke_addpoint(&p, p.mval, p.pressure); + + /* handle errors while adding point */ + if ((ok == GP_STROKEADD_FULL) || (ok == GP_STROKEADD_OVERFLOW)) { + /* finish off old stroke */ + gp_paint_strokeend(&p); + + /* start a new stroke, starting from previous point */ + gp_stroke_addpoint(&p, p.mvalo, p.opressure); + ok= gp_stroke_addpoint(&p, p.mval, p.pressure); + } + else if (ok == GP_STROKEADD_INVALID) { + /* the painting operation cannot continue... */ + error("Cannot paint stroke"); + p.status = GP_STATUS_ERROR; + + if (G.f & G_DEBUG) + printf("Error: Grease-Pencil Paint - Add Point Invalid \n"); + // XXX break; + } + // XXX force_draw(0); + + p.mvalo[0]= p.mval[0]; + p.mvalo[1]= p.mval[1]; + p.opressure= p.pressure; + } + + /* do mouse checking at the end, so don't check twice, and potentially + * miss a short tap + */ + } + + /* clear edit flags */ + G.f &= ~G_GREASEPENCIL; + + /* restore cursor to indicate end of drawing */ + // XXX (cursor callbacks in regiontype) setcursor_space(p.sa->spacetype, CURSOR_STD); + + /* check size of buffer before cleanup, to determine if anything happened here */ + if (paintmode == GP_PAINTMODE_ERASER) { + ok= 1; /* assume that we did something... */ + // XXX draw_sel_circle(NULL, p.mvalo, 0, p.radius, 0); + } + else + ok= p.gpd->sbuffer_size; + + /* cleanup */ + gp_paint_cleanup(&p); + gp_session_cleanup(&p); + + /* done! return if a stroke was successfully added */ + return ok; +} + + +/* All event (loops) handling checking if stroke drawing should be initiated + * should call this function. + */ +short gpencil_do_paint (bContext *C) +{ + ScrArea *sa= CTX_wm_area(C); + bGPdata *gpd = gpencil_data_getactive(sa); + short retval= 0; + int alt= 0, shift= 0, mbut= 0; // XXX + + /* check if possible to do painting */ + if (gpd == NULL) + return 0; + + /* currently, we will only 'paint' if: + * 1. draw-mode on gpd is set (for accessibility reasons) + * a) single dots are only available by this method if a single click is made + * b) a straight line is drawn if ctrl-modifier is held (check is done when stroke is converted!) + * 2. if shift-modifier is held + lmb -> 'quick paint' + * + * OR + * + * draw eraser stroke if: + * 1. using the eraser on a tablet + * 2. draw-mode on gpd is set (for accessiblity reasons) + * (eraser is mapped to right-mouse) + * 3. Alt + 'select' mouse-button + * i.e. if LMB = select: Alt-LMB + * if RMB = select: Alt-RMB + */ + if (get_activedevice() == 2) { + /* eraser on a tablet - always try to erase strokes */ + retval = gpencil_paint(C, GP_PAINTMODE_ERASER); + } + else if (gpd->flag & GP_DATA_EDITPAINT) { + /* try to paint/erase */ + if (mbut == L_MOUSE) + retval = gpencil_paint(C, GP_PAINTMODE_DRAW); + else if (mbut == R_MOUSE) + retval = gpencil_paint(C, GP_PAINTMODE_ERASER); + } + else if (!(gpd->flag & GP_DATA_LMBPLOCK)) { + /* try to paint/erase as not locked */ + if (shift && (mbut == L_MOUSE)) { + retval = gpencil_paint(C, GP_PAINTMODE_DRAW); + } + else if (alt) { + if ((U.flag & USER_LMOUSESELECT) && (mbut == L_MOUSE)) + retval = gpencil_paint(C, GP_PAINTMODE_ERASER); + else if (!(U.flag & USER_LMOUSESELECT) && (mbut == R_MOUSE)) + retval = gpencil_paint(C, GP_PAINTMODE_ERASER); + } + } + + /* return result of trying to paint */ + return retval; +} + +/* ************************************************** */ diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h new file mode 100644 index 00000000000..38cee1e559c --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -0,0 +1,81 @@ +/** + * $Id: + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2009 Blender Foundation. + * All rights reserved. + * + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ +#ifndef ED_GPENCIL_INTERN_H +#define ED_GPENCIL_INTERN_H + +/* internal exports only */ +/******************************************************* */ +/* FILTERED ACTION DATA - TYPES */ + +/* XXX */ +/* This struct defines a structure used for quick access */ +typedef struct bActListElem { + struct bActListElem *next, *prev; + + void *data; /* source data this elem represents */ + int type; /* one of the ACTTYPE_* values */ + int flag; /* copy of elem's flags for quick access */ + int index; /* copy of adrcode where applicable */ + + void *key_data; /* motion data - ipo or ipo-curve */ + short datatype; /* type of motion data to expect */ + + struct bActionGroup *grp; /* action group that owns the channel */ + + void *owner; /* will either be an action channel or fake ipo-channel (for keys) */ + short ownertype; /* type of owner */ +} bActListElem; + +/******************************************************* */ +/* FILTER ACTION DATA - METHODS/TYPES */ + +/* filtering flags - under what circumstances should a channel be added */ +typedef enum ACTFILTER_FLAGS { + ACTFILTER_VISIBLE = (1<<0), /* should channels be visible */ + ACTFILTER_SEL = (1<<1), /* should channels be selected */ + ACTFILTER_FOREDIT = (1<<2), /* does editable status matter */ + ACTFILTER_CHANNELS = (1<<3), /* do we only care that it is a channel */ + ACTFILTER_IPOKEYS = (1<<4), /* only channels referencing ipo's */ + ACTFILTER_ONLYICU = (1<<5), /* only reference ipo-curves */ + ACTFILTER_FORDRAWING = (1<<6), /* make list for interface drawing */ + ACTFILTER_ACTGROUPED = (1<<7) /* belongs to the active group */ +} ACTFILTER_FLAGS; + +/* Action Editor - Main Data types */ +typedef enum ACTCONT_TYPES { + ACTCONT_NONE = 0, + ACTCONT_ACTION, + ACTCONT_SHAPEKEY, + ACTCONT_GPENCIL +} ACTCONT_TYPES; + + + + +#endif /* ED_GPENCIL_INTERN_H */ + diff --git a/source/blender/editors/include/ED_armature.h b/source/blender/editors/include/ED_armature.h index 33e8c87e6ca..c731c9ed740 100644 --- a/source/blender/editors/include/ED_armature.h +++ b/source/blender/editors/include/ED_armature.h @@ -99,6 +99,9 @@ void clear_armature(struct Scene *scene, struct Object *ob, char mode); void create_vgroups_from_armature(struct Scene *scene, struct Object *ob, struct Object *par); void docenter_armature (struct Scene *scene, struct View3D *v3d, struct Object *ob, int centermode); +void auto_align_armature(struct Scene *scene, struct View3D *v3d, short mode); +void unique_editbone_name (ListBase *edbo, char *name); + /* poseobject.c */ void ED_armature_exit_posemode(struct Base *base); void ED_armature_enter_posemode(struct Base *base); diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h new file mode 100644 index 00000000000..f3f054c2b6f --- /dev/null +++ b/source/blender/editors/include/ED_gpencil.h @@ -0,0 +1,101 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008, Blender Foundation + * This is a new part of Blender + * + * Contributor(s): Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef ED_GPENCIL_H +#define ED_GPENCIL_H + +struct ListBase; +struct bScreen; +struct ScrArea; +struct ARegion; +struct View3D; +struct SpaceNode; +struct SpaceSeq; +struct bGPdata; +struct bGPDlayer; +struct bGPDframe; +struct bGPdata; +struct uiBlock; +struct ImBuf; + + + +/* ------------- Grease-Pencil Helpers -------------- */ + +/* Temporary 'Stroke Point' data */ +typedef struct tGPspoint { + short x, y; /* x and y coordinates of cursor (in relative to area) */ + float pressure; /* pressure of tablet at this point */ +} tGPspoint; + +/* ------------ Grease-Pencil API ------------------ */ + +void free_gpencil_strokes(struct bGPDframe *gpf); +void free_gpencil_frames(struct bGPDlayer *gpl); +void free_gpencil_layers(struct ListBase *list); +void free_gpencil_data(struct bGPdata *gpd); + +struct bGPDframe *gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe); +struct bGPDlayer *gpencil_layer_addnew(struct bGPdata *gpd); +struct bGPdata *gpencil_data_addnew(void); + +struct bGPDframe *gpencil_frame_duplicate(struct bGPDframe *src); +struct bGPDlayer *gpencil_layer_duplicate(struct bGPDlayer *src); +struct bGPdata *gpencil_data_duplicate(struct bGPdata *gpd); + +struct bGPdata *gpencil_data_getactive(struct ScrArea *sa); +short gpencil_data_setactive(struct ScrArea *sa, struct bGPdata *gpd); +struct ScrArea *gpencil_data_findowner(struct bGPdata *gpd); + +void gpencil_frame_delete_laststroke(struct bGPDframe *gpf); + +struct bGPDframe *gpencil_layer_getframe(struct bGPDlayer *gpl, int cframe, short addnew); +void gpencil_layer_delframe(struct bGPDlayer *gpl, struct bGPDframe *gpf); +struct bGPDlayer *gpencil_layer_getactive(struct bGPdata *gpd); +void gpencil_layer_setactive(struct bGPdata *gpd, struct bGPDlayer *active); +void gpencil_layer_delactive(struct bGPdata *gpd); + +void gpencil_delete_actframe(struct bGPdata *gpd, int cfra); +void gpencil_delete_laststroke(struct bGPdata *gpd, int cfra); + +void gpencil_delete_operation(int cfra, short mode); +void gpencil_delete_menu(void); + +void gpencil_convert_operation(short mode); +void gpencil_convert_menu(void); + +short gpencil_do_paint(struct bContext *C); + +/* drawgpencil.c */ + +void draw_gpencil_2dimage(struct bContext *C, struct ImBuf *ibuf); +void draw_gpencil_2dview(struct bContext *C, short onlyv2d); +void draw_gpencil_3dview(struct bContext *C, short only3d); +void draw_gpencil_oglrender(struct bContext *C); + + +#endif /* ED_GPENCIL_H */ diff --git a/source/blender/editors/include/ED_sequencer.h b/source/blender/editors/include/ED_sequencer.h new file mode 100644 index 00000000000..2a3b5445dfe --- /dev/null +++ b/source/blender/editors/include/ED_sequencer.h @@ -0,0 +1,31 @@ +/** + * $Id: + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2009, Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef ED_SEQUENCER_H +#define ED_SEQUENCER_H + +#define SEQ_ZOOM_FAC(szoom) (szoom > 0)? (szoom) : (szoom == 0)? (1.0) : (-1.0/szoom) + + +#endif /* ED_SEQUENCER_H */ diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index f9ea8053b72..00fb017d752 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -94,6 +94,8 @@ unsigned int view3d_sample_backbuf(struct ViewContext *vc, int x, int y); #define MAXPICKBUF 10000 short view3d_opengl_select(struct ViewContext *vc, unsigned int *buffer, unsigned int bufsize, rcti *input); void view3d_set_viewcontext(struct bContext *C, struct ViewContext *vc); +/* XXX should move to arithb.c */ +int edge_inside_circle(short centx, short centy, short rad, short x1, short y1, short x2, short y2); /* modes */ void ED_view3d_exit_paint_modes(struct bContext *C); diff --git a/source/blender/editors/sculpt/Makefile b/source/blender/editors/sculpt/Makefile new file mode 100644 index 00000000000..6b5c8baf27a --- /dev/null +++ b/source/blender/editors/sculpt/Makefile @@ -0,0 +1,57 @@ +# +# $Id: Makefile 14 2002-10-13 15:57:19Z hans $ +# +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# The Original Code is Copyright (C) 2007 Blender Foundation +# All rights reserved. +# +# The Original Code is: all of this file. +# +# Contributor(s): none yet. +# +# ***** END GPL LICENSE BLOCK ***** +# +# Makes module object directory and bounces make to subdirectories. + +LIBNAME = ed_sculpt +DIR = $(OCGDIR)/blender/$(LIBNAME) + +include nan_compile.mk + +CFLAGS += $(LEVEL_1_C_WARNINGS) + +CPPFLAGS += -I$(NAN_GLEW)/include +CPPFLAGS += -I$(OPENGL_HEADERS) + +CPPFLAGS += -I$(NAN_BMFONT)/include +CPPFLAGS += -I$(NAN_GUARDEDALLOC)/include +CPPFLAGS += -I$(NAN_ELBEEM)/include + +CPPFLAGS += -I../../windowmanager +CPPFLAGS += -I../../blenkernel +CPPFLAGS += -I../../blenloader +CPPFLAGS += -I../../blenlib +CPPFLAGS += -I../../makesdna +CPPFLAGS += -I../../makesrna +CPPFLAGS += -I../../imbuf +CPPFLAGS += -I../../gpu +CPPFLAGS += -I../../render/extern/include + +# own include + +CPPFLAGS += -I../include diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 9c91d95fc27..267d1213fc5 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -60,6 +60,7 @@ #include "ED_anim_api.h" #include "ED_space_api.h" +#include "ED_sequencer.h" #include "ED_types.h" #include "UI_interface.h" diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h index 47d2ae6ee9b..bc6824015d9 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.h +++ b/source/blender/editors/space_sequencer/sequencer_intern.h @@ -37,8 +37,6 @@ struct SpaceSeq; struct ARegion; struct Scene; -#define SEQ_ZOOM_FAC(szoom) (szoom > 0)? (szoom) : (szoom == 0)? (1.0) : (-1.0/szoom) - /* sequencer_header.c */ void sequencer_header_buttons(const struct bContext *C, struct ARegion *ar); diff --git a/source/blender/editors/space_view3d/view3d_header.c b/source/blender/editors/space_view3d/view3d_header.c index c862562dcff..f7662913bf6 100644 --- a/source/blender/editors/space_view3d/view3d_header.c +++ b/source/blender/editors/space_view3d/view3d_header.c @@ -3298,15 +3298,15 @@ static void do_view3d_edit_curve_controlpointsmenu(bContext *C, void *arg, int e clear_tilt(); break; case 2: /* Free */ - sethandlesNurb(3); + sethandlesNurb(editnurb, 3); DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); break; case 3: /* vector */ - sethandlesNurb(2); + sethandlesNurb(editnurb, 2); DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); break; case 4: /* smooth */ - sethandlesNurb(1); + sethandlesNurb(editnurb, 1); DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); break; case 5: /* make vertex parent */ |