Issue exposed by rB4c7b1766a7f1. Main idea is that non-memfile first undo step should check into previous memfile and tag the ID it is editing as `future_changed`. That way, when we go back and undo to the memfile, said IDs are properly detected as changed and re-read from the memfile. Otherwise, undo system sees them as unchanged, and just re-use the current data instead. Note that currently only Sculpt mode seems affected (probably because it is storing the mode switch itself as a Sculpt undo step instead of a memfile one), but similar action might be needed in some other cases too. Maniphest Tasks: T82388 Differential Revision: https://developer.blender.org/D9510
456 lines
14 KiB
C
456 lines
14 KiB
C
/*
|
|
* 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) 2020 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup edsculpt
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_hash.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_task.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_modifier_types.h"
|
|
|
|
#include "BKE_brush.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_mesh_mapping.h"
|
|
#include "BKE_modifier.h"
|
|
#include "BKE_object.h"
|
|
#include "BKE_paint.h"
|
|
#include "BKE_particle.h"
|
|
#include "BKE_pbvh.h"
|
|
#include "BKE_pointcache.h"
|
|
#include "BKE_scene.h"
|
|
#include "BKE_screen.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_message.h"
|
|
#include "WM_toolsystem.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "ED_object.h"
|
|
#include "ED_screen.h"
|
|
#include "ED_sculpt.h"
|
|
#include "ED_undo.h"
|
|
#include "ED_view3d.h"
|
|
#include "paint_intern.h"
|
|
#include "sculpt_intern.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_resources.h"
|
|
|
|
#include "bmesh.h"
|
|
#include "bmesh_tools.h"
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
|
|
void SCULPT_dynamic_topology_triangulate(BMesh *bm)
|
|
{
|
|
if (bm->totloop != bm->totface * 3) {
|
|
BM_mesh_triangulate(
|
|
bm, MOD_TRIANGULATE_QUAD_BEAUTY, MOD_TRIANGULATE_NGON_EARCLIP, 4, false, NULL, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
void SCULPT_pbvh_clear(Object *ob)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
/* Clear out any existing DM and PBVH. */
|
|
if (ss->pbvh) {
|
|
BKE_pbvh_free(ss->pbvh);
|
|
ss->pbvh = NULL;
|
|
}
|
|
|
|
if (ss->pmap) {
|
|
MEM_freeN(ss->pmap);
|
|
ss->pmap = NULL;
|
|
}
|
|
|
|
if (ss->pmap_mem) {
|
|
MEM_freeN(ss->pmap_mem);
|
|
ss->pmap_mem = NULL;
|
|
}
|
|
|
|
BKE_object_free_derived_caches(ob);
|
|
|
|
/* Tag to rebuild PBVH in depsgraph. */
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
|
}
|
|
|
|
void SCULPT_dyntopo_node_layers_add(SculptSession *ss)
|
|
{
|
|
int cd_node_layer_index;
|
|
|
|
char layer_id[] = "_dyntopo_node_id";
|
|
|
|
cd_node_layer_index = CustomData_get_named_layer_index(&ss->bm->vdata, CD_PROP_INT32, layer_id);
|
|
if (cd_node_layer_index == -1) {
|
|
BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_INT32, layer_id);
|
|
cd_node_layer_index = CustomData_get_named_layer_index(
|
|
&ss->bm->vdata, CD_PROP_INT32, layer_id);
|
|
}
|
|
|
|
ss->cd_vert_node_offset = CustomData_get_n_offset(
|
|
&ss->bm->vdata,
|
|
CD_PROP_INT32,
|
|
cd_node_layer_index - CustomData_get_layer_index(&ss->bm->vdata, CD_PROP_INT32));
|
|
|
|
ss->bm->vdata.layers[cd_node_layer_index].flag |= CD_FLAG_TEMPORARY;
|
|
|
|
cd_node_layer_index = CustomData_get_named_layer_index(&ss->bm->pdata, CD_PROP_INT32, layer_id);
|
|
if (cd_node_layer_index == -1) {
|
|
BM_data_layer_add_named(ss->bm, &ss->bm->pdata, CD_PROP_INT32, layer_id);
|
|
cd_node_layer_index = CustomData_get_named_layer_index(
|
|
&ss->bm->pdata, CD_PROP_INT32, layer_id);
|
|
}
|
|
|
|
ss->cd_face_node_offset = CustomData_get_n_offset(
|
|
&ss->bm->pdata,
|
|
CD_PROP_INT32,
|
|
cd_node_layer_index - CustomData_get_layer_index(&ss->bm->pdata, CD_PROP_INT32));
|
|
|
|
ss->bm->pdata.layers[cd_node_layer_index].flag |= CD_FLAG_TEMPORARY;
|
|
}
|
|
|
|
void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
Mesh *me = ob->data;
|
|
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(me);
|
|
|
|
SCULPT_pbvh_clear(ob);
|
|
|
|
ss->bm_smooth_shading = (scene->toolsettings->sculpt->flags & SCULPT_DYNTOPO_SMOOTH_SHADING) !=
|
|
0;
|
|
|
|
/* Dynamic topology doesn't ensure selection state is valid, so remove T36280. */
|
|
BKE_mesh_mselect_clear(me);
|
|
|
|
/* Create triangles-only BMesh. */
|
|
ss->bm = BM_mesh_create(&allocsize,
|
|
&((struct BMeshCreateParams){
|
|
.use_toolflags = false,
|
|
}));
|
|
|
|
BM_mesh_bm_from_me(ss->bm,
|
|
me,
|
|
(&(struct BMeshFromMeshParams){
|
|
.calc_face_normal = true,
|
|
.use_shapekey = true,
|
|
.active_shapekey = ob->shapenr,
|
|
}));
|
|
SCULPT_dynamic_topology_triangulate(ss->bm);
|
|
BM_data_layer_add(ss->bm, &ss->bm->vdata, CD_PAINT_MASK);
|
|
SCULPT_dyntopo_node_layers_add(ss);
|
|
/* Make sure the data for existing faces are initialized. */
|
|
if (me->totpoly != ss->bm->totface) {
|
|
BM_mesh_normals_update(ss->bm);
|
|
}
|
|
|
|
/* Enable dynamic topology. */
|
|
me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY;
|
|
|
|
/* Enable logging for undo/redo. */
|
|
ss->bm_log = BM_log_create(ss->bm);
|
|
|
|
/* Update dependency graph, so modifiers that depend on dyntopo being enabled
|
|
* are re-evaluated and the PBVH is re-created. */
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
|
BKE_scene_graph_update_tagged(depsgraph, bmain);
|
|
}
|
|
|
|
/* Free the sculpt BMesh and BMLog
|
|
*
|
|
* If 'unode' is given, the BMesh's data is copied out to the unode
|
|
* before the BMesh is deleted so that it can be restored from. */
|
|
static void SCULPT_dynamic_topology_disable_ex(
|
|
Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, SculptUndoNode *unode)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
Mesh *me = ob->data;
|
|
|
|
SCULPT_pbvh_clear(ob);
|
|
|
|
if (unode) {
|
|
/* Free all existing custom data. */
|
|
CustomData_free(&me->vdata, me->totvert);
|
|
CustomData_free(&me->edata, me->totedge);
|
|
CustomData_free(&me->fdata, me->totface);
|
|
CustomData_free(&me->ldata, me->totloop);
|
|
CustomData_free(&me->pdata, me->totpoly);
|
|
|
|
/* Copy over stored custom data. */
|
|
SculptUndoNodeGeometry *geometry = &unode->geometry_bmesh_enter;
|
|
me->totvert = geometry->totvert;
|
|
me->totloop = geometry->totloop;
|
|
me->totpoly = geometry->totpoly;
|
|
me->totedge = geometry->totedge;
|
|
me->totface = 0;
|
|
CustomData_copy(
|
|
&geometry->vdata, &me->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, geometry->totvert);
|
|
CustomData_copy(
|
|
&geometry->edata, &me->edata, CD_MASK_MESH.emask, CD_DUPLICATE, geometry->totedge);
|
|
CustomData_copy(
|
|
&geometry->ldata, &me->ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, geometry->totloop);
|
|
CustomData_copy(
|
|
&geometry->pdata, &me->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, geometry->totpoly);
|
|
|
|
BKE_mesh_update_customdata_pointers(me, false);
|
|
}
|
|
else {
|
|
BKE_sculptsession_bm_to_me(ob, true);
|
|
|
|
/* Reset Face Sets as they are no longer valid. */
|
|
if (!CustomData_has_layer(&me->pdata, CD_SCULPT_FACE_SETS)) {
|
|
CustomData_add_layer(&me->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, me->totpoly);
|
|
}
|
|
ss->face_sets = CustomData_get_layer(&me->pdata, CD_SCULPT_FACE_SETS);
|
|
for (int i = 0; i < me->totpoly; i++) {
|
|
ss->face_sets[i] = 1;
|
|
}
|
|
me->face_sets_color_default = 1;
|
|
|
|
/* Sync the visibility to vertices manually as the pmap is still not initialized. */
|
|
for (int i = 0; i < me->totvert; i++) {
|
|
me->mvert[i].flag &= ~ME_HIDE;
|
|
me->mvert[i].flag |= ME_VERT_PBVH_UPDATE;
|
|
}
|
|
}
|
|
|
|
/* Clear data. */
|
|
me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY;
|
|
|
|
/* Typically valid but with global-undo they can be NULL, see: T36234. */
|
|
if (ss->bm) {
|
|
BM_mesh_free(ss->bm);
|
|
ss->bm = NULL;
|
|
}
|
|
if (ss->bm_log) {
|
|
BM_log_free(ss->bm_log);
|
|
ss->bm_log = NULL;
|
|
}
|
|
|
|
BKE_particlesystem_reset_all(ob);
|
|
BKE_ptcache_object_reset(scene, ob, PTCACHE_RESET_OUTDATED);
|
|
|
|
/* Update dependency graph, so modifiers that depend on dyntopo being enabled
|
|
* are re-evaluated and the PBVH is re-created. */
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
|
BKE_scene_graph_update_tagged(depsgraph, bmain);
|
|
}
|
|
|
|
void SCULPT_dynamic_topology_disable(bContext *C, SculptUndoNode *unode)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Object *ob = CTX_data_active_object(C);
|
|
SCULPT_dynamic_topology_disable_ex(bmain, depsgraph, scene, ob, unode);
|
|
}
|
|
|
|
void sculpt_dynamic_topology_disable_with_undo(Main *bmain,
|
|
Depsgraph *depsgraph,
|
|
Scene *scene,
|
|
Object *ob)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
if (ss->bm != NULL) {
|
|
/* May be false in background mode. */
|
|
const bool use_undo = G.background ? (ED_undo_stack_get() != NULL) : true;
|
|
if (use_undo) {
|
|
SCULPT_undo_push_begin(ob, "Dynamic topology disable");
|
|
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_END);
|
|
}
|
|
SCULPT_dynamic_topology_disable_ex(bmain, depsgraph, scene, ob, NULL);
|
|
if (use_undo) {
|
|
SCULPT_undo_push_end();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sculpt_dynamic_topology_enable_with_undo(Main *bmain,
|
|
Depsgraph *depsgraph,
|
|
Scene *scene,
|
|
Object *ob)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
if (ss->bm == NULL) {
|
|
/* May be false in background mode. */
|
|
const bool use_undo = G.background ? (ED_undo_stack_get() != NULL) : true;
|
|
if (use_undo) {
|
|
SCULPT_undo_push_begin(ob, "Dynamic topology enable");
|
|
}
|
|
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob);
|
|
if (use_undo) {
|
|
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_BEGIN);
|
|
SCULPT_undo_push_end();
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sculpt_dynamic_topology_toggle_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
WM_cursor_wait(true);
|
|
|
|
if (ss->bm) {
|
|
sculpt_dynamic_topology_disable_with_undo(bmain, depsgraph, scene, ob);
|
|
}
|
|
else {
|
|
sculpt_dynamic_topology_enable_with_undo(bmain, depsgraph, scene, ob);
|
|
}
|
|
|
|
WM_cursor_wait(false);
|
|
WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int dyntopo_warning_popup(bContext *C, wmOperatorType *ot, enum eDynTopoWarnFlag flag)
|
|
{
|
|
uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Warning!"), ICON_ERROR);
|
|
uiLayout *layout = UI_popup_menu_layout(pup);
|
|
|
|
if (flag & (DYNTOPO_WARN_VDATA | DYNTOPO_WARN_EDATA | DYNTOPO_WARN_LDATA)) {
|
|
const char *msg_error = TIP_("Vertex Data Detected!");
|
|
const char *msg = TIP_("Dyntopo will not preserve vertex colors, UVs, or other customdata");
|
|
uiItemL(layout, msg_error, ICON_INFO);
|
|
uiItemL(layout, msg, ICON_NONE);
|
|
uiItemS(layout);
|
|
}
|
|
|
|
if (flag & DYNTOPO_WARN_MODIFIER) {
|
|
const char *msg_error = TIP_("Generative Modifiers Detected!");
|
|
const char *msg = TIP_(
|
|
"Keeping the modifiers will increase polycount when returning to object mode");
|
|
|
|
uiItemL(layout, msg_error, ICON_INFO);
|
|
uiItemL(layout, msg, ICON_NONE);
|
|
uiItemS(layout);
|
|
}
|
|
|
|
uiItemFullO_ptr(layout, ot, IFACE_("OK"), ICON_NONE, NULL, WM_OP_EXEC_DEFAULT, 0, NULL);
|
|
|
|
UI_popup_menu_end(C, pup);
|
|
|
|
return OPERATOR_INTERFACE;
|
|
}
|
|
|
|
enum eDynTopoWarnFlag SCULPT_dynamic_topology_check(Scene *scene, Object *ob)
|
|
{
|
|
Mesh *me = ob->data;
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
enum eDynTopoWarnFlag flag = 0;
|
|
|
|
BLI_assert(ss->bm == NULL);
|
|
UNUSED_VARS_NDEBUG(ss);
|
|
|
|
for (int i = 0; i < CD_NUMTYPES; i++) {
|
|
if (!ELEM(i, CD_MVERT, CD_MEDGE, CD_MFACE, CD_MLOOP, CD_MPOLY, CD_PAINT_MASK, CD_ORIGINDEX)) {
|
|
if (CustomData_has_layer(&me->vdata, i)) {
|
|
flag |= DYNTOPO_WARN_VDATA;
|
|
}
|
|
if (CustomData_has_layer(&me->edata, i)) {
|
|
flag |= DYNTOPO_WARN_EDATA;
|
|
}
|
|
if (CustomData_has_layer(&me->ldata, i)) {
|
|
flag |= DYNTOPO_WARN_LDATA;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
VirtualModifierData virtualModifierData;
|
|
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData);
|
|
|
|
/* Exception for shape keys because we can edit those. */
|
|
for (; md; md = md->next) {
|
|
const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
|
|
if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) {
|
|
continue;
|
|
}
|
|
|
|
if (mti->type == eModifierTypeType_Constructive) {
|
|
flag |= DYNTOPO_WARN_MODIFIER;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return flag;
|
|
}
|
|
|
|
static int sculpt_dynamic_topology_toggle_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent *UNUSED(event))
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
if (!ss->bm) {
|
|
Scene *scene = CTX_data_scene(C);
|
|
enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob);
|
|
|
|
if (flag) {
|
|
/* The mesh has customdata that will be lost, let the user confirm this is OK. */
|
|
return dyntopo_warning_popup(C, op->type, flag);
|
|
}
|
|
}
|
|
|
|
return sculpt_dynamic_topology_toggle_exec(C, op);
|
|
}
|
|
|
|
void SCULPT_OT_dynamic_topology_toggle(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Dynamic Topology Toggle";
|
|
ot->idname = "SCULPT_OT_dynamic_topology_toggle";
|
|
ot->description = "Dynamic topology alters the mesh topology while sculpting";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = sculpt_dynamic_topology_toggle_invoke;
|
|
ot->exec = sculpt_dynamic_topology_toggle_exec;
|
|
ot->poll = SCULPT_mode_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|