Sculpt: Implement undo of Apply Base during sculpt session

The idea is to push both base mesh geometry and PBVH coordinates
so it is possible to undo everything without loosing data which was
not flushed from sculpt session to base mesh.

It is possible do memory optimization to avoid push custom data
layers which are not touched by operator, but before doing that
better to ensure this is a correct and working approach.

Differential Revision: https://developer.blender.org/D7381
This commit is contained in:
Sergey Sharybin 2020-03-30 09:35:01 +02:00
parent 9e0b44aae9
commit b2c2d7b7f1
4 changed files with 105 additions and 2 deletions

@ -54,6 +54,11 @@ void ED_sculpt_undosys_type(struct UndoType *ut);
void ED_sculpt_undo_geometry_begin(struct Object *ob, const char *name);
void ED_sculpt_undo_geometry_end(struct Object *ob);
/* Undo for changes happening on a base mesh for multires sculpting.
* if there is no multires sculpt active regular undo is used. */
void ED_sculpt_undo_push_multires_mesh_begin(struct bContext *C, const char *str);
void ED_sculpt_undo_push_multires_mesh_end(struct bContext *C, const char *str);
#ifdef __cplusplus
}
#endif

@ -86,6 +86,7 @@
#include "ED_mesh.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "WM_api.h"
#include "WM_types.h"
@ -1697,8 +1698,12 @@ static int multires_base_apply_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
ED_sculpt_undo_push_multires_mesh_begin(C, op->type->name);
multiresModifier_base_apply(depsgraph, object, mmd);
ED_sculpt_undo_push_multires_mesh_end(C, op->type->name);
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
@ -1726,7 +1731,7 @@ void OBJECT_OT_multires_base_apply(wmOperatorType *ot)
ot->exec = multires_base_apply_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
ot->flag = OPTYPE_REGISTER | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
}

@ -478,6 +478,7 @@ typedef struct SculptUndoNode {
* the object when undoing the operation
*
* Modified geometry is stored after the modification and is used to redo the modification. */
bool geometry_clear_pbvh;
SculptUndoNodeGeometry geometry_original;
SculptUndoNodeGeometry geometry_modified;

@ -55,6 +55,9 @@
#include "BKE_subsurf.h"
#include "BKE_undo_system.h"
// XXX: Ideally should be no direct call to such low level things.
#include "BKE_subdiv_eval.h"
#include "DEG_depsgraph.h"
#include "WM_api.h"
@ -552,7 +555,9 @@ static void sculpt_undo_geometry_free_data(SculptUndoNodeGeometry *geometry)
static void sculpt_undo_geometry_restore(SculptUndoNode *unode, Object *object)
{
SCULPT_pbvh_clear(object);
if (unode->geometry_clear_pbvh) {
SCULPT_pbvh_clear(object);
}
if (unode->applied) {
sculpt_undo_geometry_restore_data(&unode->geometry_modified, object);
@ -740,6 +745,10 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
}
}
if (subdiv_ccg != NULL) {
BKE_subdiv_eval_refine_from_mesh(subdiv_ccg->subdiv, ob->data, NULL);
}
if (update || rebuild) {
bool tag_update = false;
/* We update all nodes still, should be more clever, but also
@ -1079,6 +1088,7 @@ static SculptUndoNode *sculpt_undo_geometry_push(Object *object, SculptUndoType
{
SculptUndoNode *unode = sculpt_undo_find_or_alloc_node_type(object, type);
unode->applied = false;
unode->geometry_clear_pbvh = true;
SculptUndoNodeGeometry *geometry = sculpt_undo_geometry_get(unode);
sculpt_undo_geometry_store_data(geometry, object);
@ -1510,3 +1520,85 @@ static UndoSculpt *sculpt_undo_get_nodes(void)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Undo for changes happening on a base mesh for multires sculpting.
*
* Use this for multires operators which changes base mesh and which are to be
* possible. Example of such operators is Apply Base.
*
* Usage:
*
* static int operator_exec((bContext *C, wmOperator *op) {
*
* ED_sculpt_undo_push_mixed_begin(C, op->type->name);
* // Modify base mesh.
* ED_sculpt_undo_push_mixed_end(C, op->type->name);
*
* return OPERATOR_FINISHED;
* }
*
* If object is not in sculpt mode or sculpt does not happen on multires then
* regular ED_undo_push() is used.
* *
* \{ */
static bool sculpt_undo_use_multires_mesh(bContext *C)
{
if (BKE_paintmode_get_active_from_context(C) != PAINT_MODE_SCULPT) {
return false;
}
Object *object = CTX_data_active_object(C);
SculptSession *sculpt_session = object->sculpt;
return sculpt_session->multires.active;
}
static void sculpt_undo_push_all_grids(Object *object)
{
SculptSession *ss = object->sculpt;
PBVHNode **nodes;
int totnodes;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnodes);
for (int i = 0; i < totnodes; i++) {
SculptUndoNode *unode = SCULPT_undo_push_node(object, nodes[i], SCULPT_UNDO_COORDS);
unode->node = NULL;
}
MEM_SAFE_FREE(nodes);
}
void ED_sculpt_undo_push_multires_mesh_begin(bContext *C, const char *str)
{
if (!sculpt_undo_use_multires_mesh(C)) {
return;
}
Object *object = CTX_data_active_object(C);
SCULPT_undo_push_begin(str);
SculptUndoNode *geometry_unode = SCULPT_undo_push_node(object, NULL, SCULPT_UNDO_GEOMETRY);
geometry_unode->geometry_clear_pbvh = false;
sculpt_undo_push_all_grids(object);
}
void ED_sculpt_undo_push_multires_mesh_end(bContext *C, const char *str)
{
if (!sculpt_undo_use_multires_mesh(C)) {
ED_undo_push(C, str);
return;
}
Object *object = CTX_data_active_object(C);
SculptUndoNode *geometry_unode = SCULPT_undo_push_node(object, NULL, SCULPT_UNDO_GEOMETRY);
geometry_unode->geometry_clear_pbvh = false;
SCULPT_undo_push_end();
}
/** \} */