This commit merge the full development done in greasepencil-object branch and include mainly the following features. - New grease pencil object. - New drawing engine. - New grease pencil modes Draw/Sculpt/Edit and Weight Paint. - New brushes for grease pencil. - New modifiers for grease pencil. - New shaders FX. - New material system (replace old palettes and colors). - Split of annotations (old grease pencil) and new grease pencil object. - UI adapted to blender 2.8. You can get more info here: https://code.blender.org/2017/12/drawing-2d-animation-in-blender-2-8/ https://code.blender.org/2018/07/grease-pencil-status-update/ This is the result of nearly two years of development and I want thanks firstly the other members of the grease pencil team: Daniel M. Lara, Matias Mendiola and Joshua Leung for their support, ideas and to keep working in the project all the time, without them this project had been impossible. Also, I want thanks other Blender developers for their help, advices and to be there always to help me, and specially to Clément Foucault, Dalai Felinto, Pablo Vázquez and Campbell Barton.
580 lines
15 KiB
C
580 lines
15 KiB
C
/*
|
|
* ***** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2004 Blender Foundation
|
|
* All rights reserved.
|
|
*
|
|
* The Original Code is: all of this file.
|
|
*
|
|
* Contributor(s): none yet.
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file blender/editors/undo/ed_undo.c
|
|
* \ingroup edundo
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "CLG_log.h"
|
|
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_callbacks.h"
|
|
#include "BLI_listbase.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "BKE_blender_undo.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_screen.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_undo_system.h"
|
|
#include "BKE_workspace.h"
|
|
#include "BKE_paint.h"
|
|
|
|
#include "ED_gpencil.h"
|
|
#include "ED_render.h"
|
|
#include "ED_object.h"
|
|
#include "ED_screen.h"
|
|
#include "ED_undo.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
#include "WM_toolsystem.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_resources.h"
|
|
|
|
/** We only need this locally. */
|
|
static CLG_LogRef LOG = {"ed.undo"};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Generic Undo System Access
|
|
*
|
|
* Non-operator undo editor functions.
|
|
* \{ */
|
|
|
|
void ED_undo_push(bContext *C, const char *str)
|
|
{
|
|
CLOG_INFO(&LOG, 1, "name='%s'", str);
|
|
|
|
const int steps = U.undosteps;
|
|
|
|
if (steps <= 0) {
|
|
return;
|
|
}
|
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
|
|
/* Only apply limit if this is the last undo step. */
|
|
if (wm->undo_stack->step_active && (wm->undo_stack->step_active->next == NULL)) {
|
|
BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, steps - 1, 0);
|
|
}
|
|
|
|
BKE_undosys_step_push(wm->undo_stack, C, str);
|
|
|
|
if (U.undomemory != 0) {
|
|
const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024;
|
|
BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, 0, memory_limit);
|
|
}
|
|
|
|
WM_file_tag_modified();
|
|
}
|
|
|
|
/* note: also check undo_history_exec() in bottom if you change notifiers */
|
|
static int ed_undo_step(bContext *C, int step, const char *undoname)
|
|
{
|
|
CLOG_INFO(&LOG, 1, "name='%s', step=%d", undoname, step);
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *win = CTX_wm_window(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
ScrArea *sa = CTX_wm_area(C);
|
|
|
|
/* undo during jobs are running can easily lead to freeing data using by jobs,
|
|
* or they can just lead to freezing job in some other cases */
|
|
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* TODO(campbell): undo_system: use undo system */
|
|
/* grease pencil can be can be used in plenty of spaces, so check it first */
|
|
if (ED_gpencil_session_active()) {
|
|
return ED_undo_gpencil_step(C, step, undoname);
|
|
}
|
|
if (sa && (sa->spacetype == SPACE_VIEW3D)) {
|
|
Object *obact = CTX_data_active_object(C);
|
|
if (obact && (obact->type == OB_GPENCIL)) {
|
|
ED_gpencil_toggle_brush_cursor(C, false, NULL);
|
|
}
|
|
}
|
|
|
|
UndoStep *step_data_from_name = NULL;
|
|
int step_for_callback = step;
|
|
if (undoname != NULL) {
|
|
step_data_from_name = BKE_undosys_step_find_by_name(wm->undo_stack, undoname);
|
|
if (step_data_from_name == NULL) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* TODO(campbell), could use simple optimization. */
|
|
/* Pointers match on redo. */
|
|
step_for_callback = (
|
|
BLI_findindex(&wm->undo_stack->steps, step_data_from_name) <
|
|
BLI_findindex(&wm->undo_stack->steps, wm->undo_stack->step_active)) ? 1 : -1;
|
|
}
|
|
|
|
/* App-Handlers (pre). */
|
|
{
|
|
/* Note: ignore grease pencil for now. */
|
|
Main *bmain = CTX_data_main(C);
|
|
wm->op_undo_depth++;
|
|
BLI_callback_exec(bmain, &scene->id, (step_for_callback > 0) ? BLI_CB_EVT_UNDO_PRE : BLI_CB_EVT_REDO_PRE);
|
|
wm->op_undo_depth--;
|
|
}
|
|
|
|
|
|
/* Undo System */
|
|
{
|
|
if (undoname) {
|
|
BKE_undosys_step_undo_with_data(wm->undo_stack, C, step_data_from_name);
|
|
}
|
|
else {
|
|
BKE_undosys_step_undo_compat_only(wm->undo_stack, C, step);
|
|
}
|
|
|
|
/* Set special modes for grease pencil */
|
|
if (sa && (sa->spacetype == SPACE_VIEW3D)) {
|
|
Object *obact = CTX_data_active_object(C);
|
|
if (obact && (obact->type == OB_GPENCIL)) {
|
|
/* set cursor */
|
|
if (ELEM(obact->mode, OB_MODE_GPENCIL_PAINT, OB_MODE_GPENCIL_SCULPT, OB_MODE_GPENCIL_WEIGHT)) {
|
|
ED_gpencil_toggle_brush_cursor(C, true, NULL);
|
|
}
|
|
else {
|
|
ED_gpencil_toggle_brush_cursor(C, false, NULL);
|
|
}
|
|
/* set workspace mode */
|
|
Base *basact = CTX_data_active_base(C);
|
|
ED_object_base_activate(C, basact);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* App-Handlers (post). */
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
scene = CTX_data_scene(C);
|
|
wm->op_undo_depth++;
|
|
BLI_callback_exec(bmain, &scene->id, step_for_callback > 0 ? BLI_CB_EVT_UNDO_PRE : BLI_CB_EVT_REDO_PRE);
|
|
wm->op_undo_depth--;
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
|
WM_event_add_notifier(C, NC_WM | ND_UNDO, NULL);
|
|
|
|
Main *bmain = CTX_data_main(C);
|
|
WM_toolsystem_refresh_screen_all(bmain);
|
|
|
|
if (win) {
|
|
win->addmousemove = true;
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void ED_undo_grouped_push(bContext *C, const char *str)
|
|
{
|
|
/* do nothing if previous undo task is the same as this one (or from the same undo group) */
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
const UndoStep *us = wm->undo_stack->step_active;
|
|
if (us && STREQ(str, us->name)) {
|
|
BKE_undosys_stack_clear_active(wm->undo_stack);
|
|
}
|
|
|
|
/* push as usual */
|
|
ED_undo_push(C, str);
|
|
}
|
|
|
|
void ED_undo_pop(bContext *C)
|
|
{
|
|
ed_undo_step(C, 1, NULL);
|
|
}
|
|
void ED_undo_redo(bContext *C)
|
|
{
|
|
ed_undo_step(C, -1, NULL);
|
|
}
|
|
|
|
void ED_undo_push_op(bContext *C, wmOperator *op)
|
|
{
|
|
/* in future, get undo string info? */
|
|
ED_undo_push(C, op->type->name);
|
|
}
|
|
|
|
void ED_undo_grouped_push_op(bContext *C, wmOperator *op)
|
|
{
|
|
if (op->type->undo_group[0] != '\0') {
|
|
ED_undo_grouped_push(C, op->type->undo_group);
|
|
}
|
|
else {
|
|
ED_undo_grouped_push(C, op->type->name);
|
|
}
|
|
}
|
|
|
|
void ED_undo_pop_op(bContext *C, wmOperator *op)
|
|
{
|
|
/* search back a couple of undo's, in case something else added pushes */
|
|
ed_undo_step(C, 0, op->type->name);
|
|
}
|
|
|
|
/* name optionally, function used to check for operator redo panel */
|
|
bool ED_undo_is_valid(const bContext *C, const char *undoname)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
return BKE_undosys_stack_has_undo(wm->undo_stack, undoname);
|
|
}
|
|
|
|
/**
|
|
* Ideally we wont access the stack directly,
|
|
* this is needed for modes which handle undo themselves (bypassing #ED_undo_push).
|
|
*
|
|
* Using global isn't great, this just avoids doing inline,
|
|
* causing 'BKE_global.h' & 'BKE_main.h' includes.
|
|
*/
|
|
UndoStack *ED_undo_stack_get(void)
|
|
{
|
|
wmWindowManager *wm = G_MAIN->wm.first;
|
|
return wm->undo_stack;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Undo, Undo Push & Redo Operators
|
|
* \{ */
|
|
|
|
static int ed_undo_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
/* "last operator" should disappear, later we can tie this with undo stack nicer */
|
|
WM_operator_stack_clear(CTX_wm_manager(C));
|
|
return ed_undo_step(C, 1, NULL);
|
|
}
|
|
|
|
static int ed_undo_push_exec(bContext *C, wmOperator *op)
|
|
{
|
|
char str[BKE_UNDO_STR_MAX];
|
|
RNA_string_get(op->ptr, "message", str);
|
|
ED_undo_push(C, str);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int ed_redo_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
return ed_undo_step(C, -1, NULL);
|
|
}
|
|
|
|
static int ed_undo_redo_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
wmOperator *last_op = WM_operator_last_redo(C);
|
|
const int ret = ED_undo_operator_repeat(C, last_op);
|
|
return ret ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
|
|
}
|
|
|
|
static bool ed_undo_redo_poll(bContext *C)
|
|
{
|
|
wmOperator *last_op = WM_operator_last_redo(C);
|
|
return last_op && ED_operator_screenactive(C) &&
|
|
WM_operator_check_ui_enabled(C, last_op->type->name);
|
|
}
|
|
|
|
void ED_OT_undo(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Undo";
|
|
ot->description = "Undo previous action";
|
|
ot->idname = "ED_OT_undo";
|
|
|
|
/* api callbacks */
|
|
ot->exec = ed_undo_exec;
|
|
ot->poll = ED_operator_screenactive;
|
|
}
|
|
|
|
void ED_OT_undo_push(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Undo Push";
|
|
ot->description = "Add an undo state (internal use only)";
|
|
ot->idname = "ED_OT_undo_push";
|
|
|
|
/* api callbacks */
|
|
ot->exec = ed_undo_push_exec;
|
|
|
|
ot->flag = OPTYPE_INTERNAL;
|
|
|
|
RNA_def_string(ot->srna, "message", "Add an undo step *function may be moved*", BKE_UNDO_STR_MAX, "Undo Message", "");
|
|
}
|
|
|
|
void ED_OT_redo(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Redo";
|
|
ot->description = "Redo previous action";
|
|
ot->idname = "ED_OT_redo";
|
|
|
|
/* api callbacks */
|
|
ot->exec = ed_redo_exec;
|
|
ot->poll = ED_operator_screenactive;
|
|
}
|
|
|
|
void ED_OT_undo_redo(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Undo and Redo";
|
|
ot->description = "Undo and redo previous action";
|
|
ot->idname = "ED_OT_undo_redo";
|
|
|
|
/* api callbacks */
|
|
ot->exec = ed_undo_redo_exec;
|
|
ot->poll = ed_undo_redo_poll;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Operator Repeat
|
|
* \{ */
|
|
|
|
/* ui callbacks should call this rather than calling WM_operator_repeat() themselves */
|
|
int ED_undo_operator_repeat(bContext *C, wmOperator *op)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (op) {
|
|
CLOG_INFO(&LOG, 1, "idname='%s'", op->type->idname);
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
struct Scene *scene = CTX_data_scene(C);
|
|
|
|
/* keep in sync with logic in view3d_panel_operator_redo() */
|
|
ARegion *ar_orig = CTX_wm_region(C);
|
|
ARegion *ar_win = BKE_area_find_region_active_win(CTX_wm_area(C));
|
|
|
|
if (ar_win) {
|
|
CTX_wm_region_set(C, ar_win);
|
|
}
|
|
|
|
if ((WM_operator_repeat_check(C, op)) &&
|
|
(WM_operator_poll(C, op->type)) &&
|
|
/* note, undo/redo cant run if there are jobs active,
|
|
* check for screen jobs only so jobs like material/texture/world preview
|
|
* (which copy their data), wont stop redo, see [#29579]],
|
|
*
|
|
* note, - WM_operator_check_ui_enabled() jobs test _must_ stay in sync with this */
|
|
(WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY) == 0))
|
|
{
|
|
int retval;
|
|
|
|
if (G.debug & G_DEBUG)
|
|
printf("redo_cb: operator redo %s\n", op->type->name);
|
|
|
|
WM_operator_free_all_after(wm, op);
|
|
|
|
ED_undo_pop_op(C, op);
|
|
|
|
if (op->type->check) {
|
|
if (op->type->check(C, op)) {
|
|
/* check for popup and re-layout buttons */
|
|
ARegion *ar_menu = CTX_wm_menu(C);
|
|
if (ar_menu) {
|
|
ED_region_tag_refresh_ui(ar_menu);
|
|
}
|
|
}
|
|
}
|
|
|
|
retval = WM_operator_repeat(C, op);
|
|
if ((retval & OPERATOR_FINISHED) == 0) {
|
|
if (G.debug & G_DEBUG)
|
|
printf("redo_cb: operator redo failed: %s, return %d\n", op->type->name, retval);
|
|
ED_undo_redo(C);
|
|
}
|
|
else {
|
|
ret = 1;
|
|
}
|
|
}
|
|
else {
|
|
if (G.debug & G_DEBUG) {
|
|
printf("redo_cb: WM_operator_repeat_check returned false %s\n", op->type->name);
|
|
}
|
|
}
|
|
|
|
/* set region back */
|
|
CTX_wm_region_set(C, ar_orig);
|
|
}
|
|
else {
|
|
CLOG_WARN(&LOG, "called with NULL 'op'");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void ED_undo_operator_repeat_cb(bContext *C, void *arg_op, void *UNUSED(arg_unused))
|
|
{
|
|
ED_undo_operator_repeat(C, (wmOperator *)arg_op);
|
|
}
|
|
|
|
void ED_undo_operator_repeat_cb_evt(bContext *C, void *arg_op, int UNUSED(arg_event))
|
|
{
|
|
ED_undo_operator_repeat(C, (wmOperator *)arg_op);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Undo History Operator
|
|
* \{ */
|
|
|
|
/* create enum based on undo items */
|
|
static const EnumPropertyItem *rna_undo_itemf(bContext *C, int *totitem)
|
|
{
|
|
EnumPropertyItem item_tmp = {0}, *item = NULL;
|
|
int i = 0;
|
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
if (wm->undo_stack == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
for (UndoStep *us = wm->undo_stack->steps.first; us; us = us->next, i++) {
|
|
if (us->skip == false) {
|
|
item_tmp.identifier = us->name;
|
|
item_tmp.name = IFACE_(us->name);
|
|
if (us == wm->undo_stack->step_active) {
|
|
item_tmp.icon = ICON_HIDE_OFF;
|
|
}
|
|
else {
|
|
item_tmp.icon = ICON_NONE;
|
|
}
|
|
item_tmp.value = i;
|
|
RNA_enum_item_add(&item, totitem, &item_tmp);
|
|
}
|
|
}
|
|
RNA_enum_item_end(&item, totitem);
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
static int undo_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
|
|
{
|
|
int totitem = 0;
|
|
|
|
{
|
|
const EnumPropertyItem *item = rna_undo_itemf(C, &totitem);
|
|
|
|
if (totitem > 0) {
|
|
uiPopupMenu *pup = UI_popup_menu_begin(C, RNA_struct_ui_name(op->type->srna), ICON_NONE);
|
|
uiLayout *layout = UI_popup_menu_layout(pup);
|
|
uiLayout *split = uiLayoutSplit(layout, 0.0f, false);
|
|
uiLayout *column = NULL;
|
|
const int col_size = 20 + totitem / 12;
|
|
int i, c;
|
|
bool add_col = true;
|
|
|
|
for (c = 0, i = totitem; i--;) {
|
|
if (add_col && !(c % col_size)) {
|
|
column = uiLayoutColumn(split, false);
|
|
add_col = false;
|
|
}
|
|
if (item[i].identifier) {
|
|
uiItemIntO(column, item[i].name, item[i].icon, op->type->idname, "item", item[i].value);
|
|
++c;
|
|
add_col = true;
|
|
}
|
|
}
|
|
|
|
MEM_freeN((void *)item);
|
|
|
|
UI_popup_menu_end(C, pup);
|
|
}
|
|
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* note: also check ed_undo_step() in top if you change notifiers */
|
|
static int undo_history_exec(bContext *C, wmOperator *op)
|
|
{
|
|
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "item");
|
|
if (RNA_property_is_set(op->ptr, prop)) {
|
|
int item = RNA_property_int_get(op->ptr, prop);
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
BKE_undosys_step_undo_from_index(wm->undo_stack, C, item);
|
|
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void ED_OT_undo_history(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Undo History";
|
|
ot->description = "Redo specific action in history";
|
|
ot->idname = "ED_OT_undo_history";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = undo_history_invoke;
|
|
ot->exec = undo_history_exec;
|
|
ot->poll = ED_operator_screenactive;
|
|
|
|
RNA_def_int(ot->srna, "item", 0, 0, INT_MAX, "Item", "", 0, INT_MAX);
|
|
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Undo Helper Functions
|
|
* \{ */
|
|
|
|
void ED_undo_object_set_active_or_warn(ViewLayer *view_layer, Object *ob, const char *info, CLG_LogRef *log)
|
|
{
|
|
Object *ob_prev = OBACT(view_layer);
|
|
if (ob_prev != ob) {
|
|
Base *base = BKE_view_layer_base_find(view_layer, ob);
|
|
if (base != NULL) {
|
|
view_layer->basact = base;
|
|
}
|
|
else {
|
|
/* Should never fail, may not crash but can give odd behavior. */
|
|
CLOG_WARN(log, "'%s' failed to restore active object: '%s'", info, ob->id.name + 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|