diff options
author | Campbell Barton <ideasman42@gmail.com> | 2018-03-19 16:17:59 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2018-03-31 21:40:37 +0300 |
commit | 651b8fb14eb6ee5cbfa98bffe80a966a0753b14e (patch) | |
tree | 2281978509d82a25fb5fbf586f34335e3606442d /source/blender/editors/mesh | |
parent | 91d0825b5556150c017dad767f7971bb6a731aec (diff) |
Undo: unified undo system w/ linear history
- Use a single undo history for all operations.
- UndoType's are registered and poll the context to check if they
should be used when performing an undo push.
- Mode switching is used to ensure the state is correct before
undo data is restored.
- Some undo types accumulate changes (image & text editing)
others store the state multiple times (with de-duplication).
This is supported by checking UndoStack.mode `ACCUMULATE` / `STORE`.
- Each undo step stores ID datablocks they use with utilities to help
manage restoring correct ID's.
Needed since global undo is now mixed with other modes undo.
- Currently performs each undo step when going up/down history
Previously this wasn't done, making history fail in some cases.
This can be optimized to skip some combinations of undo steps.
grease-pencil is an exception which has not been updated
since it integrates undo into the draw-session.
See D3113
Diffstat (limited to 'source/blender/editors/mesh')
-rw-r--r-- | source/blender/editors/mesh/editmesh_undo.c | 207 |
1 files changed, 180 insertions, 27 deletions
diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index ab91f4b34c7..220f9cc0287 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -29,16 +29,24 @@ #include "DNA_key_types.h" #include "BLI_listbase.h" +#include "BLI_array_utils.h" +#include "BLI_alloca.h" #include "BKE_DerivedMesh.h" #include "BKE_context.h" #include "BKE_key.h" #include "BKE_mesh.h" #include "BKE_editmesh.h" +#include "BKE_depsgraph.h" +#include "BKE_undo_system.h" +#include "ED_object.h" #include "ED_mesh.h" #include "ED_util.h" +#include "WM_types.h" +#include "WM_api.h" + #define USE_ARRAY_STORE #ifdef USE_ARRAY_STORE @@ -60,6 +68,9 @@ # include "BLI_task.h" #endif +/* -------------------------------------------------------------------- */ +/** \name Undo Conversion + * \{ */ #ifdef USE_ARRAY_STORE @@ -95,6 +106,8 @@ typedef struct UndoMesh { BArrayState *mselect; } store; #endif /* USE_ARRAY_STORE */ + + size_t undo_size; } UndoMesh; @@ -474,23 +487,17 @@ static void um_arraystore_free(UndoMesh *um) /* for callbacks */ /* undo simply makes copies of a bmesh */ -static void *editbtMesh_to_undoMesh(void *emv, void *obdata) +static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key) { - + BLI_assert(BLI_array_is_zeroed(um, 1)); #ifdef USE_ARRAY_STORE_THREAD /* changes this waits is low, but must have finished */ if (um_arraystore.task_pool) { BLI_task_pool_work_and_wait(um_arraystore.task_pool); } #endif - - BMEditMesh *em = emv; - Mesh *obme = obdata; - - UndoMesh *um = MEM_callocN(sizeof(UndoMesh), "undo Mesh"); - /* make sure shape keys work */ - um->me.key = obme->key ? BKE_key_copy_nolib(obme->key) : NULL; + um->me.key = key ? BKE_key_copy_nolib(key) : NULL; /* BM_mesh_validate(em->bm); */ /* for troubleshooting */ @@ -536,13 +543,12 @@ static void *editbtMesh_to_undoMesh(void *emv, void *obdata) return um; } -static void undoMesh_to_editbtMesh(void *um_v, void *em_v, void *obdata) +static void undomesh_to_editmesh(UndoMesh *um, BMEditMesh *em, Mesh *obmesh) { - BMEditMesh *em = em_v, *em_tmp; + BMEditMesh *em_tmp; Object *ob = em->ob; - UndoMesh *um = um_v; BMesh *bm; - Key *key = ((Mesh *) obdata)->key; + Key *key = obmesh->key; #ifdef USE_ARRAY_STORE #ifdef USE_ARRAY_STORE_THREAD @@ -615,9 +621,8 @@ static void undoMesh_to_editbtMesh(void *um_v, void *em_v, void *obdata) #endif } -static void free_undo(void *um_v) +static void undomesh_free_data(UndoMesh *um) { - UndoMesh *um = um_v; Mesh *me = &um->me; #ifdef USE_ARRAY_STORE @@ -644,28 +649,176 @@ static void free_undo(void *um_v) } BKE_mesh_free(me); - MEM_freeN(me); } -static void *getEditMesh(bContext *C) +static Object *editmesh_object_from_context(bContext *C) { Object *obedit = CTX_data_edit_object(C); if (obedit && obedit->type == OB_MESH) { Mesh *me = obedit->data; - return me->edit_btmesh; + if (me->edit_btmesh != NULL) { + return obedit; + } } return NULL; } -/* and this is all the undo system needs to know */ -void undo_push_mesh(bContext *C, const char *name) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +typedef struct MeshUndoStep { + UndoStep step; + /* Use for all ID lookups (can be NULL). */ + struct UndoIDPtrMap *id_map; + + /* note: will split out into list for multi-object-editmode. */ + UndoRefID_Object obedit_ref; + /* Needed for MTexPoly's image use. */ + UndoRefID_Object *image_array_ref; + UndoMesh data; +} MeshUndoStep; + +static void mesh_undosys_step_encode_store_ids(MeshUndoStep *us) +{ + Mesh *me = us->obedit_ref.ptr->data; + BMesh *bm = me->edit_btmesh->bm; + const int mtex_len = CustomData_number_of_layers(&bm->pdata, CD_MTEXPOLY); + if (mtex_len != 0) { + ID **id_src = BLI_array_alloca(id_src, mtex_len); + memset(id_src, 0x0, sizeof(*id_src) * mtex_len); + + BMIter iter; + BMFace *efa; + + if (us->id_map == NULL) { + us->id_map = BKE_undosys_ID_map_create(); + } + + uint cd_poly_tex_offset_first = CustomData_get_n_offset(&bm->pdata, CD_MTEXPOLY, 0); + uint cd_poly_tex_offset_end = cd_poly_tex_offset_first + (sizeof(MTexPoly) * mtex_len); + BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) { + for (uint cd_poly_tex_offset = cd_poly_tex_offset_first, i = 0; + cd_poly_tex_offset < cd_poly_tex_offset_end; + cd_poly_tex_offset += sizeof(MTexPoly), i++) + { + const MTexPoly *tf = BM_ELEM_CD_GET_VOID_P(efa, cd_poly_tex_offset); + if (tf->tpage != NULL) { + if ((ID *)tf->tpage != id_src[i]) { + BKE_undosys_ID_map_add(us->id_map, (ID *)tf->tpage); + id_src[i] = (ID *)tf->tpage; + } + } + } + } + } +} + +static void mesh_undosys_step_decode_restore_ids(MeshUndoStep *us) { - /* em->ob gets out of date and crashes on mesh undo, - * this is an easy way to ensure its OK - * though we could investigate the matter further. */ - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = BKE_editmesh_from_object(obedit); - em->ob = obedit; + Mesh *me = us->obedit_ref.ptr->data; + BMesh *bm = me->edit_btmesh->bm; + const int mtex_len = CustomData_number_of_layers(&bm->pdata, CD_MTEXPOLY); + if (mtex_len != 0 && us->id_map) { + BMIter iter; + BMFace *efa; + + ID **id_dst = BLI_array_alloca(id_dst, mtex_len); + ID **id_src = BLI_array_alloca(id_src, mtex_len); + memset(id_src, 0x0, sizeof(*id_src) * mtex_len); + + uint cd_poly_tex_offset_first = CustomData_get_n_offset(&bm->pdata, CD_MTEXPOLY, 0); + uint cd_poly_tex_offset_end = cd_poly_tex_offset_first + (sizeof(MTexPoly) * mtex_len); + BM_ITER_MESH(efa, &iter, bm, BM_FACES_OF_MESH) { + for (uint cd_poly_tex_offset = cd_poly_tex_offset_first, i = 0; + cd_poly_tex_offset < cd_poly_tex_offset_end; + cd_poly_tex_offset += sizeof(MTexPoly), i++) + { + MTexPoly *tf = BM_ELEM_CD_GET_VOID_P(efa, cd_poly_tex_offset); + if (tf->tpage != NULL) { + if ((ID *)tf->tpage == id_src[i]) { + tf->tpage = (Image *)id_dst[i]; + } + else { + id_src[i] = (ID *)tf->tpage; + tf->tpage = (Image *)BKE_undosys_ID_map_lookup(us->id_map, (ID *)tf->tpage); + id_dst[i] = (ID *)tf->tpage; + } + } + } + } + } +} + +static bool mesh_undosys_poll(bContext *C) +{ + return editmesh_object_from_context(C) != NULL; +} + +static bool mesh_undosys_step_encode(struct bContext *C, UndoStep *us_p) +{ + MeshUndoStep *us = (MeshUndoStep *)us_p; + us->obedit_ref.ptr = editmesh_object_from_context(C); + Mesh *me = us->obedit_ref.ptr->data; + undomesh_from_editmesh(&us->data, me->edit_btmesh, me->key); + mesh_undosys_step_encode_store_ids(us); + us->step.data_size = us->data.undo_size; + return true; +} + +static void mesh_undosys_step_decode(struct bContext *C, UndoStep *us_p, int UNUSED(dir)) +{ + /* TODO(campbell): undo_system: use low-level API to set mode. */ + ED_object_mode_set(C, OB_MODE_EDIT); + BLI_assert(mesh_undosys_poll(C)); + + MeshUndoStep *us = (MeshUndoStep *)us_p; + Object *obedit = us->obedit_ref.ptr; + Mesh *me = obedit->data; + BMEditMesh *em = me->edit_btmesh; + undomesh_to_editmesh(&us->data, em, obedit->data); + mesh_undosys_step_decode_restore_ids(us); + DAG_id_tag_update(&obedit->id, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); +} + +static void mesh_undosys_step_free(UndoStep *us_p) +{ + MeshUndoStep *us = (MeshUndoStep *)us_p; + undomesh_free_data(&us->data); + + if (us->id_map != NULL) { + BKE_undosys_ID_map_destroy(us->id_map); + } +} + +static void mesh_undosys_foreach_ID_ref( + UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data) +{ + MeshUndoStep *us = (MeshUndoStep *)us_p; + foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->obedit_ref)); + if (us->id_map != NULL) { + BKE_undosys_ID_map_foreach_ID_ref(us->id_map, foreach_ID_ref_fn, user_data); + } +} + +/* Export for ED_undo_sys. */ +void ED_mesh_undosys_type(UndoType *ut) +{ + ut->name = "Edit Mesh"; + ut->poll = mesh_undosys_poll; + ut->step_encode = mesh_undosys_step_encode; + ut->step_decode = mesh_undosys_step_decode; + ut->step_free = mesh_undosys_step_free; + + ut->step_foreach_ID_ref = mesh_undosys_foreach_ID_ref; - undo_editmode_push(C, name, getEditMesh, free_undo, undoMesh_to_editbtMesh, editbtMesh_to_undoMesh, NULL); + ut->mode = BKE_UNDOTYPE_MODE_STORE; + ut->use_context = true; + + ut->step_size = sizeof(MeshUndoStep); } + +/** \} */ |