Implement grouped undo option for operators

This option makes an operator to not push a task to the undo stack if the previous stored elemen is the same operator or part of the same undo group.

The main usage is for animation, so you can change frames to inspect the
poses, and revert the previous pose without having to roll back tons of
"change frame" operator, or even see the undo stack full.

This complements rB13ee9b8e
Design with help by Sergey Sharybin.

Reviewers: sergey, mont29

Reviewed By: mont29, sergey

Subscribers: pyc0d3r, hjalti, Severin, lowercase, brecht, monio, aligorith, hadrien, jbakker

Differential Revision: https://developer.blender.org/D2330
This commit is contained in:
Dalai Felinto 2016-11-15 11:50:11 +01:00
parent 445274fc4f
commit 69470e36d6
Notes: blender-bot 2023-02-14 07:25:43 +01:00
Referenced by issue #50001, Time required for rendering in cycle increased in the latest build.
9 changed files with 92 additions and 9 deletions

View File

@ -42,6 +42,7 @@ extern bool BKE_undo_is_valid(const char *name);
extern void BKE_undo_reset(void);
extern void BKE_undo_number(struct bContext *C, int nr);
extern const char *BKE_undo_get_name(int nr, bool *r_active);
extern const char *BKE_undo_get_name_last(void);
extern bool BKE_undo_save_file(const char *filename);
extern struct Main *BKE_undo_get_main(struct Scene **r_scene);

View File

@ -319,6 +319,13 @@ const char *BKE_undo_get_name(int nr, bool *r_active)
return NULL;
}
/* return the name of the last item */
const char *BKE_undo_get_name_last()
{
UndoElem *uel = undobase.last;
return (uel ? uel->name : NULL);
}
/**
* Saves .blend using undo buffer.
*

View File

@ -57,6 +57,7 @@
#include "ED_anim_api.h"
#include "ED_screen.h"
#include "ED_sequencer.h"
#include "ED_util.h"
#include "anim_intern.h"
@ -263,7 +264,8 @@ static void ANIM_OT_change_frame(wmOperatorType *ot)
ot->poll = change_frame_poll;
/* flags */
ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR | OPTYPE_UNDO_GROUPED;
ot->undo_group = "FRAME_CHANGE";
/* rna */
ot->prop = RNA_def_int(ot->srna, "frame", 0, MINAFRAME, MAXFRAME, "Frame", "", MINAFRAME, MAXFRAME);

View File

@ -52,6 +52,8 @@ void ED_OT_flush_edits(struct wmOperatorType *ot);
/* undo.c */
void ED_undo_push(struct bContext *C, const char *str);
void ED_undo_push_op(struct bContext *C, struct wmOperator *op);
void ED_undo_grouped_push(struct bContext *C, const char *str);
void ED_undo_grouped_push_op(struct bContext *C, struct wmOperator *op);
void ED_undo_pop_op(struct bContext *C, struct wmOperator *op);
void ED_undo_pop(struct bContext *C);
void ED_undo_redo(struct bContext *C);

View File

@ -2136,7 +2136,8 @@ static void SCREEN_OT_frame_offset(wmOperatorType *ot)
ot->exec = frame_offset_exec;
ot->poll = ED_operator_screenactive_norender;
ot->flag = 0;
ot->flag = OPTYPE_UNDO_GROUPED;
ot->undo_group = "FRAME_CHANGE";
/* rna */
RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
@ -2189,7 +2190,8 @@ static void SCREEN_OT_frame_jump(wmOperatorType *ot)
ot->exec = frame_jump_exec;
ot->poll = ED_operator_screenactive_norender;
ot->flag = OPTYPE_UNDO;
ot->flag = OPTYPE_UNDO_GROUPED;
ot->undo_group = "FRAME_CHANGE";
/* rna */
RNA_def_boolean(ot->srna, "end", 0, "Last Frame", "Jump to the last frame of the frame range");
@ -2295,7 +2297,8 @@ static void SCREEN_OT_keyframe_jump(wmOperatorType *ot)
ot->exec = keyframe_jump_exec;
ot->poll = ED_operator_screenactive_norender;
ot->flag = OPTYPE_UNDO;
ot->flag = OPTYPE_UNDO_GROUPED;
ot->undo_group = "FRAME_CHANGE";
/* properties */
RNA_def_boolean(ot->srna, "next", true, "Next Keyframe", "");
@ -2357,7 +2360,8 @@ static void SCREEN_OT_marker_jump(wmOperatorType *ot)
ot->exec = marker_jump_exec;
ot->poll = ED_operator_screenactive_norender;
ot->flag = OPTYPE_UNDO;
ot->flag = OPTYPE_UNDO_GROUPED;
ot->undo_group = "FRAME_CHANGE";
/* properties */
RNA_def_boolean(ot->srna, "next", true, "Next Marker", "");

View File

@ -217,6 +217,19 @@ static int ed_undo_step(bContext *C, int step, const char *undoname)
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) */
const char *last_undo = BKE_undo_get_name_last();
if (last_undo && STREQ(str, last_undo)) {
return;
}
/* push as usual */
ED_undo_push(C, str);
}
void ED_undo_pop(bContext *C)
{
ed_undo_step(C, 1, NULL);
@ -232,6 +245,16 @@ void ED_undo_push_op(bContext *C, wmOperator *op)
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 */

View File

@ -419,6 +419,7 @@ static EnumPropertyItem keymap_modifiers_items[] = {
static EnumPropertyItem operator_flag_items[] = {
{OPTYPE_REGISTER, "REGISTER", 0, "Register", "Display in the info window and support the redo toolbar panel"},
{OPTYPE_UNDO, "UNDO", 0, "Undo", "Push an undo event (needed for operator redo)"},
{OPTYPE_UNDO_GROUPED, "UNDO_GROUPED", 0, "Grouped Undo", "Push a single undo event for repetead instances of this operator"},
{OPTYPE_BLOCKING, "BLOCKING", 0, "Blocking", "Block anything else from using the cursor"},
{OPTYPE_MACRO, "MACRO", 0, "Macro", "Use to check if an operator is a macro"},
{OPTYPE_GRAB_CURSOR, "GRAB_CURSOR", 0, "Grab Pointer",
@ -1139,6 +1140,7 @@ static char _operator_idname[OP_MAX_TYPENAME];
static char _operator_name[OP_MAX_TYPENAME];
static char _operator_descr[RNA_DYN_DESCR_MAX];
static char _operator_ctxt[RNA_DYN_DESCR_MAX];
static char _operator_undo_group[OP_MAX_TYPENAME];
static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void *data, const char *identifier,
StructValidateFunc validate, StructCallbackFunc call, StructFreeFunc free)
{
@ -1153,10 +1155,11 @@ static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void *
dummyot.name = _operator_name; /* only assigne the pointer, string is NULL'd */
dummyot.description = _operator_descr; /* only assigne the pointer, string is NULL'd */
dummyot.translation_context = _operator_ctxt; /* only assigne the pointer, string is NULL'd */
dummyot.undo_group = _operator_undo_group; /* only assigne the pointer, string is NULL'd */
RNA_pointer_create(NULL, &RNA_Operator, &dummyop, &dummyotr);
/* clear in case they are left unset */
_operator_idname[0] = _operator_name[0] = _operator_descr[0] = '\0';
_operator_idname[0] = _operator_name[0] = _operator_descr[0] = _operator_undo_group[0] = '\0';
/* We have to set default op context! */
strcpy(_operator_ctxt, BLT_I18NCONTEXT_OPERATOR_DEFAULT);
@ -1210,9 +1213,10 @@ static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void *
int namelen = strlen(_operator_name) + 1;
int desclen = strlen(_operator_descr) + 1;
int ctxtlen = strlen(_operator_ctxt) + 1;
int ugrouplen = strlen(_operator_undo_group) + 1;
char *ch;
/* 2 terminators and 3 to convert a.b -> A_OT_b */
ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen), "_operator_idname");
ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen + ugrouplen), "_operator_idname");
WM_operator_bl_idname(ch, _operator_idname); /* convert the idname from python */
dummyot.idname = ch;
ch += idlen;
@ -1224,6 +1228,9 @@ static StructRNA *rna_Operator_register(Main *bmain, ReportList *reports, void *
ch += desclen;
strcpy(ch, _operator_ctxt);
dummyot.translation_context = ch;
ch += ctxtlen;
strcpy(ch, _operator_undo_group);
dummyot.undo_group = ch;
}
}
@ -1280,10 +1287,11 @@ static StructRNA *rna_MacroOperator_register(Main *bmain, ReportList *reports, v
dummyot.name = _operator_name; /* only assigne the pointer, string is NULL'd */
dummyot.description = _operator_descr; /* only assigne the pointer, string is NULL'd */
dummyot.translation_context = _operator_ctxt; /* only assigne the pointer, string is NULL'd */
dummyot.undo_group = _operator_undo_group; /* only assigne the pointer, string is NULL'd */
RNA_pointer_create(NULL, &RNA_Macro, &dummyop, &dummyotr);
/* clear in case they are left unset */
_operator_idname[0] = _operator_name[0] = _operator_descr[0] = '\0';
_operator_idname[0] = _operator_name[0] = _operator_descr[0] = _operator_undo_group[0] = '\0';
/* We have to set default op context! */
strcpy(_operator_ctxt, BLT_I18NCONTEXT_OPERATOR_DEFAULT);
@ -1297,9 +1305,10 @@ static StructRNA *rna_MacroOperator_register(Main *bmain, ReportList *reports, v
int namelen = strlen(_operator_name) + 1;
int desclen = strlen(_operator_descr) + 1;
int ctxtlen = strlen(_operator_ctxt) + 1;
int ugrouplen = strlen(_operator_undo_group) + 1;
char *ch;
/* 2 terminators and 3 to convert a.b -> A_OT_b */
ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen), "_operator_idname");
ch = MEM_callocN(sizeof(char) * (idlen + namelen + desclen + ctxtlen + ugrouplen), "_operator_idname");
WM_operator_bl_idname(ch, _operator_idname); /* convert the idname from python */
dummyot.idname = ch;
ch += idlen;
@ -1311,6 +1320,9 @@ static StructRNA *rna_MacroOperator_register(Main *bmain, ReportList *reports, v
ch += desclen;
strcpy(ch, _operator_ctxt);
dummyot.translation_context = ch;
ch += ctxtlen;
strcpy(ch, _operator_undo_group);
dummyot.undo_group = ch;
}
if (strlen(identifier) >= sizeof(dummyop.idname)) {
@ -1401,6 +1413,16 @@ static void rna_Operator_bl_description_set(PointerRNA *ptr, const char *value)
assert(!"setting the bl_description on a non-builtin operator");
}
static void rna_Operator_bl_undo_group_set(PointerRNA *ptr, const char *value)
{
wmOperator *data = (wmOperator *)(ptr->data);
char *str = (char *)data->type->undo_group;
if (!str[0])
BLI_strncpy(str, value, OP_MAX_TYPENAME); /* utf8 already ensured */
else
assert(!"setting the bl_undo_group on a non-builtin operator");
}
static void rna_KeyMapItem_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
{
wmKeyMapItem *kmi = ptr->data;
@ -1509,6 +1531,14 @@ static void rna_def_operator(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */
prop = RNA_def_property(srna, "bl_undo_group", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "type->undo_group");
RNA_def_property_string_maxlength(prop, OP_MAX_TYPENAME); /* else it uses the pointer size! */
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_Operator_bl_undo_group_set");
/* RNA_def_property_clear_flag(prop, PROP_EDITABLE); */
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */
prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "type->flag");
RNA_def_property_enum_items(prop, operator_flag_items);
@ -1587,6 +1617,14 @@ static void rna_def_macro_operator(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */
prop = RNA_def_property(srna, "bl_undo_group", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "type->undo_group");
RNA_def_property_string_maxlength(prop, OP_MAX_TYPENAME); /* else it uses the pointer size! */
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_Operator_bl_undo_group_set");
/* RNA_def_property_clear_flag(prop, PROP_EDITABLE); */
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_clear_flag(prop, PROP_NEVER_NULL); /* check for NULL */
prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "type->flag");
RNA_def_property_enum_items(prop, operator_flag_items);

View File

@ -138,6 +138,7 @@ enum {
OPTYPE_INTERNAL = (1 << 6),
OPTYPE_LOCK_BYPASS = (1 << 7), /* Allow operator to run when interface is locked */
OPTYPE_UNDO_GROUPED = (1 << 8), /* Special type of undo which doesn't store itself multiple times */
};
/* context to call operator in for WM_operator_name_call */
@ -522,6 +523,7 @@ typedef struct wmOperatorType {
const char *idname; /* unique identifier */
const char *translation_context;
const char *description; /* tooltips and python docs */
const char *undo_group; /* identifier to group operators together */
/* this callback executes the operator without any interactive input,
* parameters may be provided through operator properties. cannot use

View File

@ -730,6 +730,8 @@ static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat)
if (wm->op_undo_depth == 0)
if (op->type->flag & OPTYPE_UNDO)
ED_undo_push_op(C, op);
else if (op->type->flag & OPTYPE_UNDO_GROUPED)
ED_undo_grouped_push_op(C, op);
if (repeat == 0) {
if (G.debug & G_DEBUG_WM) {
@ -1857,6 +1859,8 @@ static int wm_handler_fileselect_do(bContext *C, ListBase *handlers, wmEventHand
if (CTX_wm_manager(C) == wm && wm->op_undo_depth == 0)
if (handler->op->type->flag & OPTYPE_UNDO)
ED_undo_push_op(C, handler->op);
else if (handler->op->type->flag & OPTYPE_UNDO_GROUPED)
ED_undo_grouped_push_op(C, handler->op);
if (handler->op->reports->list.first) {