Sculpt: Rewrite BMLog to support customdata and geometry undo steps #113539

Open
Joseph Eagar wants to merge 18 commits from JosephEagar/blender:temp-dyntopo-bmlog into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
14 changed files with 2346 additions and 1017 deletions

View File

@ -785,6 +785,9 @@ void BKE_sculpt_attribute_destroy_temporary_all(Object *ob);
/* Destroy attributes that were marked as stroke only in SculptAttributeParams. */
void BKE_sculpt_attributes_destroy_temporary_stroke(Object *ob);
/* Ensures the internal sculpt attribute references are valid. */
void BKE_sculpt_update_attribute_refs(Object *ob);
BLI_INLINE void *BKE_sculpt_vertex_attr_get(const PBVHVertRef vertex, const SculptAttribute *attr)
{
if (attr->data) {

View File

@ -71,6 +71,7 @@
#include "BLO_read_write.hh"
#include "bmesh.h"
#include "bmesh_log.h"
using blender::float3;
using blender::MutableSpan;
@ -1493,15 +1494,15 @@ void BKE_sculptsession_free(Object *ob)
if (ss->bm) {
BKE_sculptsession_bm_to_me(ob, true);
if (ss->bm_log) {
BM_log_free(ss->bm, ss->bm_log);
}
BM_mesh_free(ss->bm);
}
sculptsession_free_pbvh(ob);
if (ss->bm_log) {
BM_log_free(ss->bm_log);
}
if (ss->tex_pool) {
BKE_image_pool_free(ss->tex_pool);
}
@ -2833,6 +2834,11 @@ static void sculpt_attribute_update_refs(Object *ob)
}
}
void BKE_sculpt_update_attribute_refs(Object *ob)
{
sculpt_attribute_update_refs(ob);
}
void BKE_sculpt_attribute_destroy_temporary_all(Object *ob)
{
SculptSession *ss = ob->sculpt;

View File

@ -23,6 +23,7 @@
#include "DRW_pbvh.hh"
#include "bmesh.h"
#include "bmesh_log.h"
#include "pbvh_intern.hh"
using blender::Array;
@ -448,8 +449,8 @@ static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh,
node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated;
/* Log the new vertex. */
BM_log_vert_added(pbvh->bm_log, v, cd_vert_mask_offset);
/* Log the new vertex */
BM_log_vert_added(pbvh->header.bm, pbvh->bm_log, v);
return v;
}
@ -478,8 +479,8 @@ static BMFace *pbvh_bmesh_face_create(PBVH *pbvh,
node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated;
node->flag &= ~PBVH_FullyHidden;
/* Log the new face. */
BM_log_face_added(pbvh->bm_log, f);
/* Log the new face */
BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f);
return f;
}
@ -609,8 +610,8 @@ static void pbvh_bmesh_face_remove(PBVH *pbvh, BMFace *f)
f_node->bm_faces.remove(f);
BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE);
/* Log removed face. */
BM_log_face_removed(pbvh->bm_log, f);
/* Log removed face */
BM_log_face_removed(pbvh->header.bm, pbvh->bm_log, f);
/* Mark node for update. */
f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated;
@ -1274,7 +1275,7 @@ static void pbvh_bmesh_collapse_edge(
if ((v_tri[j] != v_del) && (v_tri[j]->e == nullptr)) {
pbvh_bmesh_vert_remove(pbvh, v_tri[j]);
BM_log_vert_removed(pbvh->bm_log, v_tri[j], eq_ctx->cd_vert_mask_offset);
BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v_tri[j]);
if (v_tri[j] == v_conn) {
v_conn = nullptr;
@ -1288,7 +1289,7 @@ static void pbvh_bmesh_collapse_edge(
/* Move v_conn to the midpoint of v_conn and v_del (if v_conn still exists, it
* may have been deleted above). */
if (v_conn != nullptr) {
BM_log_vert_before_modified(pbvh->bm_log, v_conn, eq_ctx->cd_vert_mask_offset);
BM_log_vert_before_modified(pbvh->header.bm, pbvh->bm_log, v_conn);
mid_v3_v3v3(v_conn->co, v_conn->co, v_del->co);
add_v3_v3(v_conn->no, v_del->no);
normalize_v3(v_conn->no);
@ -1304,7 +1305,7 @@ static void pbvh_bmesh_collapse_edge(
/* Delete v_del */
BLI_assert(!BM_vert_face_check(v_del));
BM_log_vert_removed(pbvh->bm_log, v_del, eq_ctx->cd_vert_mask_offset);
BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v_del);
/* v_conn == nullptr is OK */
BLI_ghash_insert(deleted_verts, v_del, v_conn);
BM_vert_kill(&bm, v_del);

View File

@ -67,6 +67,8 @@ set(SRC
intern/bmesh_delete.h
intern/bmesh_edgeloop.cc
intern/bmesh_edgeloop.h
intern/bmesh_idmap.cc
intern/bmesh_idmap.hh
intern/bmesh_inline.h
intern/bmesh_interp.cc
intern/bmesh_interp.h
@ -162,6 +164,7 @@ set(SRC
# public includes
bmesh.h
bmesh_log.h
bmesh_tools.h
)

View File

@ -0,0 +1,2 @@
#include "intern/bmesh_log.h"
Review

This file should be removed for now. We can change to more granular bmesh includes as a separate step later.

This file should be removed for now. We can change to more granular bmesh includes as a separate step later.

View File

@ -0,0 +1,417 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
JosephEagar marked this conversation as resolved Outdated

Missing copyright here and GPL license here

Missing copyright here and GPL license here
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
*/
#include "MEM_guardedalloc.h"
#include "BLI_assert.h"
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "BLI_index_range.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "BKE_customdata.h"
#include "DNA_customdata_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "bmesh_idmap.hh"
#include <cstdarg>
#include <cstdio>
using namespace blender;
/* Controls when `BMIdMap.free_idx_map` is used.
* When the size of `BMIdMap.freelist` goes above `FREELIST_HASHMAP_THRESHOLD_HIGH`
* then `BMIdMap.free_idx_map` will be created. When it goes below
* `FREELIST_HASHMAP_THRESHOLD_LOW` it will be freed.
*/
#define FREELIST_HASHMAP_THRESHOLD_HIGH 1024
#define FREELIST_HASHMAP_THRESHOLD_LOW 700
const char *BM_idmap_attr_name_get(int htype)
{
switch (htype) {
case BM_VERT:
return ".sculpt.vertex_id";
case BM_EDGE:
return ".sculpt.edge_id";
case BM_LOOP:
return ".sculpt.corner_id";
case BM_FACE:
return ".sculpt.face_id";
default:
BLI_assert_unreachable();
return "error";
}
}
static void idmap_log_message(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
BMIdMap *BM_idmap_new(BMesh *bm, int elem_mask)
{
BMIdMap *idmap = MEM_new<BMIdMap>("BMIdMap");
for (int i = 0; i < ARRAY_SIZE(idmap->cd_id_off); i++) {
idmap->cd_id_off[i] = -1;
}
idmap->flag = elem_mask;
idmap->bm = bm;
idmap->maxid = BM_ID_NONE + 1;
BM_idmap_check_attributes(idmap);
return idmap;
}
template<typename T> static constexpr char get_elem_type()
{
if constexpr (std::is_same_v<T, BMVert>) {
return BM_VERT;
}
else if constexpr (std::is_same_v<T, BMEdge>) {
return BM_EDGE;
}
else if constexpr (std::is_same_v<T, BMLoop>) {
return BM_LOOP;
}
else if constexpr (std::is_same_v<T, BMFace>) {
return BM_FACE;
}
}
static void idmap_grow_map(BMIdMap *idmap, int newid)
{
if (idmap->map.size() <= newid) {
idmap->map.resize(newid + 1);
}
}
void BM_idmap_delete_attributes_mesh(Mesh *me)
{
CustomData_free_layer_named(&me->vert_data, BM_idmap_attr_name_get(BM_VERT), me->totvert);
CustomData_free_layer_named(&me->edge_data, BM_idmap_attr_name_get(BM_EDGE), me->totedge);
CustomData_free_layer_named(&me->loop_data, BM_idmap_attr_name_get(BM_LOOP), me->totloop);
CustomData_free_layer_named(&me->face_data, BM_idmap_attr_name_get(BM_FACE), me->faces_num);
}
void BM_idmap_delete_attributes(BMesh *bm)
{
BM_data_layer_free_named(bm, &bm->vdata, BM_idmap_attr_name_get(BM_VERT));
BM_data_layer_free_named(bm, &bm->edata, BM_idmap_attr_name_get(BM_EDGE));
BM_data_layer_free_named(bm, &bm->ldata, BM_idmap_attr_name_get(BM_LOOP));
BM_data_layer_free_named(bm, &bm->pdata, BM_idmap_attr_name_get(BM_FACE));
}
void BM_idmap_check_ids(BMIdMap *idmap)
{
BMIter iter;
BMVert *v;
BMEdge *e;
BMFace *f;
BM_idmap_check_attributes(idmap);
idmap->freelist.clear();
if (idmap->free_idx_map) {
MEM_delete(idmap->free_idx_map);
idmap->free_idx_map = nullptr;
}
int max_id = 1;
if (idmap->flag & BM_VERT) {
BM_ITER_MESH (v, &iter, idmap->bm, BM_VERTS_OF_MESH) {
int id = BM_ELEM_CD_GET_INT(v, idmap->cd_id_off[BM_VERT]);
max_id = max_ii(max_id, id);
}
}
if (idmap->flag & BM_EDGE) {
BM_ITER_MESH (e, &iter, idmap->bm, BM_EDGES_OF_MESH) {
int id = BM_ELEM_CD_GET_INT(e, idmap->cd_id_off[BM_EDGE]);
max_id = max_ii(max_id, id);
}
}
if (idmap->flag & (BM_FACE | BM_LOOP)) {
BM_ITER_MESH (f, &iter, idmap->bm, BM_FACES_OF_MESH) {
if (idmap->flag & BM_FACE) {
int id = BM_ELEM_CD_GET_INT(f, idmap->cd_id_off[BM_FACE]);
max_id = max_ii(max_id, id);
}
if (idmap->flag & BM_LOOP) {
BMLoop *l = f->l_first;
do {
int id = BM_ELEM_CD_GET_INT(l, idmap->cd_id_off[BM_LOOP]);
max_id = max_ii(max_id, id);
} while ((l = l->next) != f->l_first);
}
}
}
max_id++;
if (idmap->map.size() <= max_id) {
idmap->map.resize(max_id);
}
/* Zero map. */
memset(static_cast<void *>(idmap->map.data()), 0, sizeof(void *) * idmap->map.size());
auto check_elem = [&](auto *elem) {
int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]);
if (id == BM_ID_NONE || id < 0 || id >= idmap->map.size() || (idmap->map[id])) {
id = max_id++;
BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id);
}
idmap_grow_map(idmap, id);
idmap->map[id] = reinterpret_cast<BMElem *>(elem);
};
if (idmap->flag & BM_VERT) {
BM_ITER_MESH (v, &iter, idmap->bm, BM_VERTS_OF_MESH) {
check_elem(v);
}
}
if (idmap->flag & BM_EDGE) {
BM_ITER_MESH (e, &iter, idmap->bm, BM_EDGES_OF_MESH) {
check_elem(e);
}
}
if (idmap->flag & (BM_FACE | BM_LOOP)) {
BM_ITER_MESH (f, &iter, idmap->bm, BM_FACES_OF_MESH) {
check_elem(f);
if (idmap->flag & BM_LOOP) {
BMLoop *l = f->l_first;
do {
check_elem(l);
} while ((l = l->next) != f->l_first);
}
}
}
idmap->maxid = max_id + 1;
}
static bool bm_idmap_check_attr(BMIdMap *idmap, int type)
{
if (!(idmap->flag & type)) {
return false;
}
CustomData *cdata;
const char *name = BM_idmap_attr_name_get(type);
switch (type) {
case BM_VERT:
cdata = &idmap->bm->vdata;
break;
case BM_EDGE:
cdata = &idmap->bm->edata;
break;
case BM_LOOP:
cdata = &idmap->bm->ldata;
break;
case BM_FACE:
cdata = &idmap->bm->pdata;
break;
default:
BLI_assert_unreachable();
return false;
}
int idx = CustomData_get_named_layer_index(cdata, CD_PROP_INT32, name);
bool exists = idx != -1;
if (!exists) {
BM_data_layer_add_named(idmap->bm, cdata, CD_PROP_INT32, name);
idx = CustomData_get_named_layer_index(cdata, CD_PROP_INT32, name);
}
idmap->cd_id_off[type] = cdata->layers[idx].offset;
return !exists;
}
bool BM_idmap_check_attributes(BMIdMap *idmap)
{
bool ret = false;
ret |= bm_idmap_check_attr(idmap, BM_VERT);
ret |= bm_idmap_check_attr(idmap, BM_EDGE);
ret |= bm_idmap_check_attr(idmap, BM_LOOP);
ret |= bm_idmap_check_attr(idmap, BM_FACE);
return ret;
}
void BM_idmap_destroy(BMIdMap *idmap)
{
if (idmap->free_idx_map) {
MEM_delete(idmap->free_idx_map);
}
MEM_delete(idmap);
}
static void check_idx_map(BMIdMap *idmap)
{
if (idmap->free_idx_map && idmap->freelist.size() < FREELIST_HASHMAP_THRESHOLD_LOW) {
MEM_delete(idmap->free_idx_map);
idmap->free_idx_map = nullptr;
}
else if (!idmap->free_idx_map && idmap->freelist.size() < FREELIST_HASHMAP_THRESHOLD_HIGH) {
idmap->free_idx_map = MEM_new<BMIdMap::FreeIdxMap>("BMIdMap::FreeIdxMap");
for (int i : IndexRange(idmap->freelist.size())) {
idmap->free_idx_map->add(idmap->freelist[i], i);
}
}
}
template<typename T> int BM_idmap_alloc(BMIdMap *idmap, T *elem)
{
int id = BM_ID_NONE;
if (idmap->freelist.size()) {
id = idmap->freelist.pop_last();
if (idmap->free_idx_map) {
idmap->free_idx_map->remove(id);
}
}
if (id == BM_ID_NONE) {
id = idmap->maxid++;
}
idmap_grow_map(idmap, id);
idmap->map[id] = reinterpret_cast<BMElem *>(elem);
BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id);
return id;
}
template<typename T> void BM_idmap_assign(BMIdMap *idmap, T *elem, int id)
{
/* Remove id from freelist. */
if (idmap->free_idx_map) {
const int *val;
if ((val = idmap->free_idx_map->lookup_ptr(id))) {
idmap->freelist[*val] = BM_ID_NONE;
idmap->free_idx_map->remove(id);
}
}
else {
for (int i : IndexRange(idmap->freelist.size())) {
if (idmap->freelist[i] == id) {
idmap->freelist[i] = BM_ID_NONE;
}
}
}
BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id);
idmap_grow_map(idmap, id);
idmap->map[id] = reinterpret_cast<BMElem *>(elem);
check_idx_map(idmap);
}
void BM_idmap_release_id(BMIdMap *idmap, int id, bool clear_id)
{
BMElem *elem = idmap->map[id];
if (elem) {
BM_idmap_release(idmap, elem, clear_id);
}
}
template<typename T> void BM_idmap_release(BMIdMap *idmap, T *elem, bool clear_id)
{
int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]);
if (id == BM_ID_NONE) {
idmap_log_message("%s: unassigned id!\n", __func__);
return;
}
if (id < 0 || id >= idmap->map.size() ||
(idmap->map[id] && idmap->map[id] != reinterpret_cast<BMElem *>(elem)))
{
idmap_log_message("%s: invalid id %d\n", __func__, id);
return;
}
else {
idmap->map[id] = nullptr;
}
idmap->freelist.append(id);
if (idmap->free_idx_map) {
idmap->free_idx_map->add(id, idmap->freelist.size() - 1);
}
check_idx_map(idmap);
if (clear_id) {
BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], BM_ID_NONE);
}
}
template<typename T> int BM_idmap_check_assign(BMIdMap *idmap, T *elem)
{
int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]);
if (id == BM_ID_NONE) {
id = BM_idmap_alloc(idmap, (BMElem *)elem);
}
return id;
}
/* Instantiate templates. */
template void BM_idmap_assign(BMIdMap *idmap, BMElem *elem, int id);
template void BM_idmap_assign(BMIdMap *idmap, BMVert *elem, int id);
template void BM_idmap_assign(BMIdMap *idmap, BMEdge *elem, int id);
template void BM_idmap_assign(BMIdMap *idmap, BMLoop *elem, int id);
template void BM_idmap_assign(BMIdMap *idmap, BMFace *elem, int id);
template int BM_idmap_check_assign(BMIdMap *idmap, BMElem *elem);
template int BM_idmap_check_assign(BMIdMap *idmap, BMVert *elem);
template int BM_idmap_check_assign(BMIdMap *idmap, BMEdge *elem);
template int BM_idmap_check_assign(BMIdMap *idmap, BMLoop *elem);
template int BM_idmap_check_assign(BMIdMap *idmap, BMFace *elem);
template int BM_idmap_alloc(BMIdMap *idmap, BMElem *elem);
template int BM_idmap_alloc(BMIdMap *idmap, BMVert *elem);
template int BM_idmap_alloc(BMIdMap *idmap, BMEdge *elem);
template int BM_idmap_alloc(BMIdMap *idmap, BMLoop *elem);
template int BM_idmap_alloc(BMIdMap *idmap, BMFace *elem);
template void BM_idmap_release(BMIdMap *idmap, BMElem *elem, bool clear_id);
template void BM_idmap_release(BMIdMap *idmap, BMVert *elem, bool clear_id);
template void BM_idmap_release(BMIdMap *idmap, BMEdge *elem, bool clear_id);
template void BM_idmap_release(BMIdMap *idmap, BMLoop *elem, bool clear_id);
template void BM_idmap_release(BMIdMap *idmap, BMFace *elem, bool clear_id);

View File

@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_sys_types.h"
/** \file
* \ingroup bmesh
*
* A simple self-contained element ID library.
* Stores IDs in integer attributes.
*/
#include "BLI_compiler_compat.h"
#include "BLI_map.hh"
#include "BLI_sys_types.h"
#include "BLI_vector.hh"
#include "bmesh.h"
#define BM_ID_NONE 0
struct BMIdMap {
int flag;
uint maxid;
int cd_id_off[15];
BMesh *bm;
blender::Vector<BMElem *> map; /* ID -> Element map. */
blender::Vector<int> freelist;
using FreeIdxMap = blender::Map<int, int>;
/* maps ids to their position within the freelist
only used if freelist is bigger then a certain size,
see FREELIST_HASHMAP_THRESHOLD_HIGH in bmesh_construct.c.*/
FreeIdxMap *free_idx_map;
};
BMIdMap *BM_idmap_new(BMesh *bm, int elem_mask);
void BM_idmap_destroy(BMIdMap *idmap);
/* Ensures idmap attributes exist. */
bool BM_idmap_check_attributes(BMIdMap *idmap);
/* Ensures every element has a unique ID. */
void BM_idmap_check_ids(BMIdMap *idmap);
/* Explicitly assign an ID. id cannot be BM_ID_NONE (zero). */
template<typename T = BMElem> void BM_idmap_assign(BMIdMap *idmap, T *elem, int id);
/* Automatically allocate an ID. */
template<typename T = BMElem> int BM_idmap_alloc(BMIdMap *idmap, T *elem);
/* Checks if an element needs an ID (it's id is BM_ID_NONE),
* and if so allocates one.
*/
template<typename T = BMElem> int BM_idmap_check_assign(BMIdMap *idmap, T *elem);
/* Release an ID; if clear_id is true the id attribute for
* that element will be set to BM_ID_NONE.
*/
template<typename T = BMElem> void BM_idmap_release(BMIdMap *idmap, T *elem, bool clear_id = true);
void BM_idmap_release_id(BMIdMap *idmap, int id, bool clear_id = true);
const char *BM_idmap_attr_name_get(int htype);
/* Deletes all id attributes. */
void BM_idmap_delete_attributes(BMesh *bm);
void BM_idmap_delete_attributes_mesh(Mesh *me);
/* Elem -> ID. */
template<typename T = BMElem> inline int BM_idmap_get_id(BMIdMap *map, T *elem)
JosephEagar marked this conversation as resolved Outdated

BLI_INLINE -> inline

`BLI_INLINE` -> `inline`
{
return BM_ELEM_CD_GET_INT(elem, map->cd_id_off[(int)elem->head.htype]);
}
/* ID -> elem. */
template<typename T = BMElem> inline T *BM_idmap_lookup(BMIdMap *map, int elem)
{
return elem >= 0 ? reinterpret_cast<T *>(map->map[elem]) : NULL;
}

File diff suppressed because it is too large Load Diff

View File

@ -4,21 +4,46 @@
#pragma once
#include "BLI_sys_types.h"
/** \file
* \ingroup bmesh
*
* `BMLog` is the undo system used by DynTopo. Changes
* to a `BMesh` are stored incrementally.
*
* The following operations are supported for logging:
* - Adding and removing vertices
* - Adding and removing edges
* - Adding and removing faces
* - Attribute changes.
* - Element header flags.
*
* Internal details:
*
* Each sculpt undo step owns a pointer to a `BMLogEntry`.
* Every `BMLogEntry` in turn has a list of log sets.
*
* A log set is a subclass of `BMLogSetBase` and can be
* either a delta set (`BMLogSetDiff`) or a full mesh
* (BMLogSetFull).
*
* Particuarly complex mesh operations can sometimes benefit from
* having a clean `BMLogSetDiff` set, this helps avoid corrupting
* element IDs. This can be done with `BM_log_entry_add_delta_set`.
*
* To log a complete mesh, use `BM_log_full_mesh`.
*/
struct BMFace;
struct BMVert;
struct BMesh;
struct RangeTreeUInt;
typedef struct BMLog BMLog;
typedef struct BMLogEntry BMLogEntry;
#ifdef __cplusplus
extern "C" {
#endif
struct BMEdge;
struct BMElem;
struct BMFace;
struct BMIdMap;
struct BMLog;
struct BMLogEntry;
struct BMVert;
struct CustomData;
/**
JosephEagar marked this conversation as resolved Outdated

It looks to me like the BMLogCallbacks aren't used. If that's so, I'd really prefer to not include them for now, to make this change more minimal and clearer.

It looks to me like the `BMLogCallbacks` aren't used. If that's so, I'd really prefer to not include them for now, to make this change more minimal and clearer.
* Allocate, initialize, and assign a new BMLog.
@ -34,17 +59,13 @@ BMLog *BM_log_create(BMesh *bm);
* The unused IDs field of the log will be initialized by taking all
* keys from all GHashes in the log entry.
*/
BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry);
BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry, bool for_redo);
/**
* Free all the data in a BMLog including the log itself.
/* Frees the BMLog.
* Note: The actual BMLogEntry instances are not freed they're
* owned by SculptUndoNode, not BMLog.
*/
void BM_log_free(BMLog *log);
/**
* Get the number of log entries.
*/
int BM_log_length(const BMLog *log);
bool BM_log_free(BMesh *bm, BMLog *log);
/** Apply a consistent ordering to BMesh vertices and faces. */
JosephEagar marked this conversation as resolved Outdated

A lot of the changes to comments in this file look unnecessary. I do think that some of the previous comments were a bit too verbose or exposed internal details, but I think it would be much nicer to change them later, to make what's actually changing in this PR clearer.

So it would be great if you could edit the diff here to only keep the necessary changes.

A lot of the changes to comments in this file look unnecessary. I do think that some of the previous comments were a bit too verbose or exposed internal details, but I think it would be much nicer to change them later, to make what's actually changing in this PR clearer. So it would be great if you could edit the diff here to only keep the necessary changes.
void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log);
@ -60,10 +81,7 @@ void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log);
*
* In either case, the new entry is set as the current log entry.
*/
BMLogEntry *BM_log_entry_add(BMLog *log);
/** Mark all used ids as unused for this node */
void BM_log_cleanup_entry(BMLogEntry *entry);
BMLogEntry *BM_log_entry_add(BMesh *bm, BMLog *log);
/**
* Remove an entry from the log.
@ -74,7 +92,19 @@ void BM_log_cleanup_entry(BMLogEntry *entry);
* This operation is only valid on the first and last entries in the
* log. Deleting from the middle will assert.
*/
void BM_log_entry_drop(BMLogEntry *entry);
bool BM_log_entry_drop(BMLogEntry *entry);
/*
* Add a new delta set to the current log entry. If no entry
* exists one will be created. Returns current log entry.
*/
BMLogEntry *BM_log_entry_add_delta_set(BMesh *bm, BMLog *log);
/* Check if customdata layout has changed. If it has a new
* subentry will be pushed so any further logging will have
* the correct customdata.
*/
BMLogEntry *BM_log_entry_check_customdata(BMesh *bm, BMLog *log);
/**
* Undo one #BMLogEntry.
@ -90,6 +120,11 @@ void BM_log_undo(BMesh *bm, BMLog *log);
*/
void BM_log_redo(BMesh *bm, BMLog *log);
/* Skip one BMLogEntry. */
void BM_log_undo_skip(BMesh *bm, BMLog *log);
/* Skip one BMLogEntry. */
void BM_log_redo_skip(BMesh *bm, BMLog *log);
/**
* Log a vertex before it is modified.
*
@ -114,7 +149,7 @@ void BM_log_redo(BMesh *bm, BMLog *log);
* state so that a subsequent redo operation will restore the newer
* vertex state.
*/
void BM_log_vert_before_modified(BMLog *log, struct BMVert *v, int cd_vert_mask_offset);
void BM_log_vert_before_modified(BMesh *bm, BMLog *log, BMVert *v);
/**
* Log a new vertex as added to the #BMesh.
@ -123,7 +158,10 @@ void BM_log_vert_before_modified(BMLog *log, struct BMVert *v, int cd_vert_mask_
* of added vertices, with the key being its ID and the value
* containing everything needed to reconstruct that vertex.
*/
void BM_log_vert_added(BMLog *log, struct BMVert *v, int cd_vert_mask_offset);
void BM_log_vert_added(BMesh *bm, BMLog *log, BMVert *v);
/* Log a new edge as added to the BMesh */
void BM_log_edge_added(BMesh *bm, BMLog *log, BMEdge *e);
/**
* Log a face before it is modified.
@ -131,7 +169,7 @@ void BM_log_vert_added(BMLog *log, struct BMVert *v, int cd_vert_mask_offset);
* This is intended to handle only header flags and we always
* assume face has been added before.
*/
void BM_log_face_modified(BMLog *log, struct BMFace *f);
void BM_log_face_modified(BMesh *bm, BMLog *log, BMFace *f);
/**
* Log a new face as added to the #BMesh.
@ -140,7 +178,7 @@ void BM_log_face_modified(BMLog *log, struct BMFace *f);
* of added faces, with the key being its ID and the value containing
* everything needed to reconstruct that face.
*/
void BM_log_face_added(BMLog *log, struct BMFace *f);
void BM_log_face_added(BMesh *bm, BMLog *log, BMFace *f);
/**
* Log a vertex as removed from the #BMesh.
@ -159,7 +197,7 @@ void BM_log_face_added(BMLog *log, struct BMFace *f);
* If there's a move record for the vertex, that's used as the
* vertices original location, then the move record is deleted.
*/
void BM_log_vert_removed(BMLog *log, struct BMVert *v, int cd_vert_mask_offset);
void BM_log_vert_removed(BMesh *bm, BMLog *log, BMVert *v);
/**
* Log a face as removed from the #BMesh.
@ -175,15 +213,12 @@ void BM_log_vert_removed(BMLog *log, struct BMVert *v, int cd_vert_mask_offset);
* its ID and the value containing everything needed to reconstruct
* that face.
*/
void BM_log_face_removed(BMLog *log, struct BMFace *f);
void BM_log_face_removed(BMesh *bm, BMLog *log, BMFace *f);
/**
* Log all vertices/faces in the #BMesh as added.
*/
void BM_log_all_added(BMesh *bm, BMLog *log);
/** Log all vertices/faces in the #BMesh as removed. */
void BM_log_before_all_removed(BMesh *bm, BMLog *log);
/* Log an edge's flags and customdata. */
void BM_log_edge_modified(BMesh *bm, BMLog *log, BMEdge *e);
/* Log an edge as removed from the BMesh */
void BM_log_edge_removed(BMesh *bm, BMLog *log, BMEdge *e);
/**
* Get the logged coordinates of a vertex.
@ -207,15 +242,13 @@ const float *BM_log_original_vert_no(BMLog *log, BMVert *v);
float BM_log_original_mask(BMLog *log, BMVert *v);
/** Get the logged data of a vertex (avoid multiple lookups). */
void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no);
void BM_log_original_vert_data(BMLog *, BMVert *v, const float **co, const float **no);
/** For internal use only (unit testing). */
BMLogEntry *BM_log_current_entry(BMLog *log);
/** For internal use only (unit testing) */
struct RangeTreeUInt *BM_log_unused_ids(BMLog *log);
/** Log the complete mesh, will be stored as
* a Mesh copy.
*/
void BM_log_full_mesh(BMesh *bm, BMLog *log);
void BM_log_print_entry(BMesh *bm, BMLogEntry *entry);
#ifdef __cplusplus
}
#endif
/* Called from sculpt undo code. */
void BM_log_print_entry(BMLogEntry *entry);
int BM_log_entry_size(BMLogEntry *entry);

View File

@ -79,6 +79,7 @@
#include "RNA_define.hh"
#include "bmesh.h"
#include "bmesh_log.h"
using blender::float3;
using blender::MutableSpan;

View File

@ -43,6 +43,7 @@
#include "UI_resources.hh"
#include "bmesh.h"
#include "bmesh_log.h"
#include "bmesh_tools.h"
using blender::IndexRange;
@ -77,7 +78,10 @@ void SCULPT_pbvh_clear(Object *ob)
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object *ob)
void SCULPT_dynamic_topology_enable_ex(Main *bmain,
Depsgraph *depsgraph,
Object *ob,
bool for_undo)
{
SculptSession *ss = ob->sculpt;
Mesh *me = static_cast<Mesh *>(ob->data);
@ -99,8 +103,24 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object
convert_params.use_shapekey = true;
convert_params.active_shapekey = ob->shapenr;
BM_mesh_bm_from_me(ss->bm, me, &convert_params);
/* Enable logging for undo/redo. */
ss->bm_log = BM_log_create(ss->bm);
/* Log full mesh prior to triangulation. */
if (for_undo) {
SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_BEGIN);
}
int faces_num = ss->bm->totface;
SCULPT_dynamic_topology_triangulate(ss->bm);
/* Now log full mesh again after triangulation. */
if (for_undo && ss->bm->totface != faces_num) {
BM_log_full_mesh(ss->bm, ss->bm_log);
Review

Why do we need to log the mesh before and after triangulation? It seems like just after triangulation would be enough, since the before state would be captured by the undo step before that.

Why do we need to log the mesh before and after triangulation? It seems like just after triangulation would be enough, since the before state would be captured by the undo step before that.
Review

I'll have to look into it. You do need two pushes per step (this is how geometry undo steps work too) but the first may've already happened at this point.

I'll have to look into it. You do need two pushes per step (this is how geometry undo steps work too) but the first may've already happened at this point.
}
BM_data_layer_add(ss->bm, &ss->bm->vdata, CD_PAINT_MASK);
/* Make sure the data for existing faces are initialized. */
@ -111,9 +131,6 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object
/* 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);
@ -125,7 +142,7 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object
* 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)
Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, SculptUndoNode * /*unode*/)
{
SculptSession *ss = ob->sculpt;
Mesh *me = static_cast<Mesh *>(ob->data);
@ -140,35 +157,18 @@ static void SCULPT_dynamic_topology_disable_ex(
SCULPT_pbvh_clear(ob);
if (unode) {
/* Free all existing custom data. */
BKE_mesh_clear_geometry(me);
BKE_sculptsession_bm_to_me(ob, true);
/* Copy over stored custom data. */
SculptUndoNodeGeometry *geometry = &unode->geometry_bmesh_enter;
me->totvert = geometry->totvert;
me->totloop = geometry->totloop;
me->faces_num = geometry->faces_num;
me->totedge = geometry->totedge;
me->totface_legacy = 0;
CustomData_copy(&geometry->vert_data, &me->vert_data, CD_MASK_MESH.vmask, geometry->totvert);
CustomData_copy(&geometry->edge_data, &me->edge_data, CD_MASK_MESH.emask, geometry->totedge);
CustomData_copy(&geometry->loop_data, &me->loop_data, CD_MASK_MESH.lmask, geometry->totloop);
CustomData_copy(&geometry->face_data, &me->face_data, CD_MASK_MESH.pmask, geometry->faces_num);
blender::implicit_sharing::copy_shared_pointer(geometry->face_offset_indices,
geometry->face_offsets_sharing_info,
&me->face_offset_indices,
&me->runtime->face_offsets_sharing_info);
if (ss->bm_log) {
BM_log_free(ss->bm, ss->bm_log);
ss->bm_log = nullptr;
}
else {
BKE_sculptsession_bm_to_me(ob, true);
/* Sync the visibility to vertices manually as the `pmap` is still not initialized. */
bool *hide_vert = (bool *)CustomData_get_layer_named_for_write(
&me->vert_data, CD_PROP_BOOL, ".hide_vert", me->totvert);
if (hide_vert != nullptr) {
memset(hide_vert, 0, sizeof(bool) * me->totvert);
}
/* Sync the visibility to vertices manually as the `pmap` is still not initialized. */
bool *hide_vert = (bool *)CustomData_get_layer_named_for_write(
&me->vert_data, CD_PROP_BOOL, ".hide_vert", me->totvert);
if (hide_vert != nullptr) {
memset(hide_vert, 0, sizeof(bool) * me->totvert);
}
/* Clear data. */
@ -178,19 +178,15 @@ static void SCULPT_dynamic_topology_disable_ex(
if (ss->bm) {
BM_mesh_free(ss->bm);
ss->bm = nullptr;
}
if (ss->bm_log) {
BM_log_free(ss->bm_log);
ss->bm_log = nullptr;
}
BKE_particlesystem_reset_all(ob);
BKE_ptcache_object_reset(scene, ob, PTCACHE_RESET_OUTDATED);
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);
/* 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)
@ -231,9 +227,8 @@ static void sculpt_dynamic_topology_enable_with_undo(Main *bmain, Depsgraph *dep
if (use_undo) {
SCULPT_undo_push_begin_ex(ob, "Dynamic topology enable");
}
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob);
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob, use_undo);
if (use_undo) {
SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_BEGIN);
SCULPT_undo_push_end(ob);
}
}

View File

@ -41,6 +41,7 @@
struct AutomaskingCache;
struct AutomaskingNodeData;
struct BMLog;
struct BMLogEntry;
struct Dial;
struct DistRayAABB_Precalc;
struct Image;
@ -223,9 +224,6 @@ struct SculptUndoNode {
SculptUndoNodeGeometry geometry_original;
SculptUndoNodeGeometry geometry_modified;
/* Geometry at the bmesh enter moment. */
SculptUndoNodeGeometry geometry_bmesh_enter;
/* pivot */
float pivot_pos[3];
float pivot_rot[4];
@ -1262,7 +1260,10 @@ enum eDynTopoWarnFlag {
ENUM_OPERATORS(eDynTopoWarnFlag, DYNTOPO_WARN_MODIFIER);
/** Enable dynamic topology; mesh will be triangulated */
void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object *ob);
void SCULPT_dynamic_topology_enable_ex(Main *bmain,
Depsgraph *depsgraph,
Object *ob,
bool for_undo = false);
void SCULPT_dynamic_topology_disable(bContext *C, SculptUndoNode *unode);
void sculpt_dynamic_topology_disable_with_undo(Main *bmain,
Depsgraph *depsgraph,

View File

@ -66,6 +66,7 @@
#include "UI_resources.hh"
#include "bmesh.h"
#include "bmesh_log.h"
#include <cmath>
#include <cstdlib>
@ -189,7 +190,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op)
* parts that symmetrize modifies). */
SCULPT_undo_push_begin(ob, op);
SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_SYMMETRIZE);
BM_log_before_all_removed(ss->bm, ss->bm_log);
BM_log_full_mesh(ss->bm, ss->bm_log);
BM_mesh_toolflags_set(ss->bm, true);
@ -208,7 +209,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op)
BM_mesh_toolflags_set(ss->bm, false);
/* Finish undo. */
BM_log_all_added(ss->bm, ss->bm_log);
BM_log_full_mesh(ss->bm, ss->bm_log);
SCULPT_undo_push_end(ob);
break;
@ -405,9 +406,8 @@ void ED_object_sculptmode_enter_ex(Main *bmain,
if (has_undo) {
SCULPT_undo_push_begin_ex(ob, "Dynamic topology enable");
}
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob);
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob, has_undo);
if (has_undo) {
SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_BEGIN);
SCULPT_undo_push_end(ob);
}
}
@ -1315,10 +1315,10 @@ static int sculpt_reveal_all_exec(bContext *C, wmOperator *op)
const int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK);
BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) {
BM_log_vert_before_modified(ss->bm_log, v, cd_mask);
BM_log_vert_before_modified(ss->bm, ss->bm_log, v);
}
BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) {
BM_log_face_modified(ss->bm_log, f);
BM_log_face_modified(ss->bm, ss->bm_log, f);
}
SCULPT_face_visibility_all_set(ss, true);

View File

@ -78,6 +78,7 @@
#include "ED_undo.hh"
#include "bmesh.h"
#include "bmesh_log.h"
#include "sculpt_intern.hh"
/* Uncomment to print the undo stack in the console on push/undo/redo. */
@ -659,7 +660,7 @@ static void sculpt_undo_bmesh_restore_generic(SculptUndoNode *unode, Object *ob,
}
/* Create empty sculpt BMesh and enable logging. */
static void sculpt_undo_bmesh_enable(Object *ob, SculptUndoNode *unode)
static void sculpt_undo_bmesh_enable(Object *ob, SculptUndoNode *unode, bool for_redo)
{
SculptSession *ss = ob->sculpt;
Mesh *me = static_cast<Mesh *>(ob->data);
@ -676,7 +677,7 @@ static void sculpt_undo_bmesh_enable(Object *ob, SculptUndoNode *unode)
me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY;
/* Restore the BMLog using saved entries. */
ss->bm_log = BM_log_from_existing_entries_create(ss->bm, unode->bm_entry);
ss->bm_log = BM_log_from_existing_entries_create(ss->bm, unode->bm_entry, for_redo);
}
static void sculpt_undo_bmesh_restore_begin(bContext *C,
@ -685,15 +686,17 @@ static void sculpt_undo_bmesh_restore_begin(bContext *C,
SculptSession *ss)
{
if (unode->applied) {
/* Load the full mesh that was saved at this step. */
BM_log_undo(ss->bm, ss->bm_log);
SCULPT_dynamic_topology_disable(C, unode);
unode->applied = false;
}
else {
sculpt_undo_bmesh_enable(ob, unode);
sculpt_undo_bmesh_enable(ob, unode, true);
/* Restore the mesh from the first log entry. */
BM_log_redo(ss->bm, ss->bm_log);
unode->applied = true;
}
}
@ -704,7 +707,7 @@ static void sculpt_undo_bmesh_restore_end(bContext *C,
SculptSession *ss)
{
if (unode->applied) {
sculpt_undo_bmesh_enable(ob, unode);
sculpt_undo_bmesh_enable(ob, unode, false);
/* Restore the mesh from the last log entry. */
BM_log_undo(ss->bm, ss->bm_log);
@ -712,6 +715,9 @@ static void sculpt_undo_bmesh_restore_end(bContext *C,
unode->applied = false;
}
else {
/* Load the full mesh that was saved at this step. */
BM_log_redo(ss->bm, ss->bm_log);
/* Disable dynamic topology sculpting. */
SCULPT_dynamic_topology_disable(C, nullptr);
unode->applied = true;
@ -1110,7 +1116,6 @@ static void sculpt_undo_free_list(ListBase *lb)
sculpt_undo_geometry_free_data(&unode->geometry_original);
sculpt_undo_geometry_free_data(&unode->geometry_modified);
sculpt_undo_geometry_free_data(&unode->geometry_bmesh_enter);
MEM_delete(unode);
@ -1479,23 +1484,19 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
unode->applied = true;
if (type == SCULPT_UNDO_DYNTOPO_END) {
unode->bm_entry = BM_log_entry_add(ss->bm_log);
BM_log_before_all_removed(ss->bm, ss->bm_log);
unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log);
BM_log_full_mesh(ss->bm, ss->bm_log);
}
else if (type == SCULPT_UNDO_DYNTOPO_BEGIN) {
/* Store a copy of the mesh's current vertices, loops, and
* faces. A full copy like this is needed because entering
* dynamic-topology immediately does topological edits
* (converting faces to triangles) that the BMLog can't
* fully restore from. */
SculptUndoNodeGeometry *geometry = &unode->geometry_bmesh_enter;
sculpt_undo_geometry_store_data(geometry, ob);
unode->bm_entry = BM_log_entry_add(ss->bm_log);
BM_log_all_added(ss->bm, ss->bm_log);
* (converting faces to triangles). */
unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log);
BM_log_full_mesh(ss->bm, ss->bm_log);
}
else {
unode->bm_entry = BM_log_entry_add(ss->bm_log);
unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log);
}
BLI_addtail(&usculpt->nodes, unode);
@ -1508,32 +1509,35 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
/* Before any vertex values get modified, ensure their
* original positions are logged. */
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) {
BM_log_vert_before_modified(ss->bm_log, vd.bm_vert, vd.cd_vert_mask_offset);
BM_log_vert_before_modified(ss->bm, ss->bm_log, vd.bm_vert);
}
BKE_pbvh_vertex_iter_end;
break;
case SCULPT_UNDO_HIDDEN: {
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) {
BM_log_vert_before_modified(ss->bm_log, vd.bm_vert, vd.cd_vert_mask_offset);
BM_log_vert_before_modified(ss->bm, ss->bm_log, vd.bm_vert);
}
BKE_pbvh_vertex_iter_end;
for (BMFace *f : BKE_pbvh_bmesh_node_faces(node)) {
BM_log_face_modified(ss->bm_log, f);
BM_log_face_modified(ss->bm, ss->bm_log, f);
}
break;
}
case SCULPT_UNDO_GEOMETRY:
case SCULPT_UNDO_DYNTOPO_BEGIN:
case SCULPT_UNDO_DYNTOPO_END:
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
case SCULPT_UNDO_GEOMETRY:
case SCULPT_UNDO_FACE_SETS:
case SCULPT_UNDO_COLOR:
break;
}
}
else if (type == SCULPT_UNDO_GEOMETRY) {
BM_log_full_mesh(ss->bm, ss->bm_log);
}
return unode;
}
@ -1973,6 +1977,11 @@ void ED_sculpt_undo_geometry_begin_ex(Object *ob, const char *name)
void ED_sculpt_undo_geometry_end(Object *ob)
{
/* Inform the sculpt system attribute layouts may have changed,
* e.g. with voxel remesh.
*/
BKE_sculpt_update_attribute_refs(ob);
SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_GEOMETRY);
SCULPT_undo_push_end(ob);
}