OpenVDB: Voxel Remesher

The voxel remesher introduces a new workflow for sculpting without any of the limitations of Dyntopo (no geometry errors or performance penalty when blocking shapes). It is also useful for simulations and 3D printing.

This commit includes:
- Voxel remesh operator, voxel size mesh property and general remesh flags.
- Paint mask reprojection.
- Geometry undo/redo for sculpt mode. This should support remesh operations as well as future tools that modify the topology of the sculpt in a single step, like trimming tools or mesh insert brushes.
- UI changes in the sculpt topbar and the mesh properties pannel.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D5407
This commit is contained in:
Pablo Dobarro 2019-08-14 18:37:46 +02:00
parent 16c28b5a67
commit 45a45f7d66
17 changed files with 634 additions and 51 deletions

View File

@ -459,6 +459,22 @@ class DATA_PT_vertex_colors(MeshButtonsPanel, Panel):
col.operator("mesh.vertex_color_add", icon='ADD', text="")
col.operator("mesh.vertex_color_remove", icon='REMOVE', text="")
class DATA_PT_remesh(MeshButtonsPanel, Panel):
bl_label = "Remesh"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
col = layout.column()
mesh = context.mesh
col.prop(mesh, "remesh_voxel_size")
col.prop(mesh, "remesh_smooth_normals")
col.prop(mesh, "remesh_preserve_paint_mask")
col.operator("object.voxel_remesh", text="Voxel Remesh")
class DATA_PT_customdata(MeshButtonsPanel, Panel):
bl_label = "Geometry Data"
@ -512,6 +528,7 @@ classes = (
DATA_PT_normals,
DATA_PT_normals_auto_smooth,
DATA_PT_texture_space,
DATA_PT_remesh,
DATA_PT_customdata,
DATA_PT_custom_props_mesh,
)

View File

@ -1139,8 +1139,38 @@ class VIEW3D_PT_sculpt_dyntopo_remesh(Panel, View3DPaintPanel):
col = flow.column()
col.operator("sculpt.detail_flood_fill")
# TODO, move to space_view3d.py
class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel):
bl_context = ".sculpt_mode" # dot on purpose (access from topbar)
bl_label = "Remesh"
bl_options = {'DEFAULT_CLOSED'}
bl_ui_units_x = 12
@classmethod
def poll(cls, context):
return (context.sculpt_object and context.tool_settings.sculpt)
def draw_header(self, context):
is_popover = self.is_popover
layout = self.layout
layout.operator(
"object.voxel_remesh",
text="",
emboss=is_popover,
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
col = layout.column()
mesh = context.active_object.data
col.prop(mesh, "remesh_voxel_size")
col.prop(mesh, "remesh_smooth_normals")
col.prop(mesh, "remesh_preserve_paint_mask")
col.operator("object.voxel_remesh", text="Remesh")
# TODO, move to space_view3d.py
class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel):
bl_context = ".sculpt_mode" # dot on purpose (access from topbar)
@ -2152,6 +2182,7 @@ classes = (
VIEW3D_PT_sculpt_options,
VIEW3D_PT_sculpt_options_unified,
VIEW3D_PT_sculpt_options_gravity,
VIEW3D_PT_sculpt_voxel_remesh,
VIEW3D_PT_tools_weightpaint_symmetry,
VIEW3D_PT_tools_weightpaint_symmetry_for_topbar,
VIEW3D_PT_tools_weightpaint_options,

View File

@ -0,0 +1,46 @@
/*
* 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) 2019 by Blender Foundation
* All rights reserved.
*/
#ifndef __BKE_REMESH_H__
#define __BKE_REMESH_H__
/** \file
* \ingroup bke
*/
#include "DNA_mesh_types.h"
#ifdef WITH_OPENVDB
# include "openvdb_capi.h"
#endif
/* OpenVDB Voxel Remesher */
#ifdef WITH_OPENVDB
struct OpenVDBLevelSet *BKE_remesh_voxel_ovdb_mesh_to_level_set_create(
Mesh *mesh, struct OpenVDBTransform *transform);
Mesh *BKE_remesh_voxel_ovdb_volume_to_mesh_nomain(struct OpenVDBLevelSet *level_set,
double isovalue,
double adaptivity,
bool relax_disoriented_triangles);
#endif
Mesh *BKE_remesh_voxel_to_mesh_nomain(Mesh *mesh, float voxel_size);
/* Data reprojection functions */
void BKE_remesh_reproject_paint_mask(Mesh *target, Mesh *source);
#endif /* __BKE_REMESH_H__ */

View File

@ -178,6 +178,7 @@ set(SRC
intern/pbvh.c
intern/pbvh_bmesh.c
intern/pointcache.c
intern/remesh.c
intern/report.c
intern/rigidbody.c
intern/scene.c
@ -317,6 +318,7 @@ set(SRC
BKE_particle.h
BKE_pbvh.h
BKE_pointcache.h
BKE_remesh.h
BKE_report.h
BKE_rigidbody.h
BKE_scene.h

View File

@ -536,6 +536,7 @@ void BKE_mesh_init(Mesh *me)
me->size[0] = me->size[1] = me->size[2] = 1.0;
me->smoothresh = DEG2RADF(30);
me->texflag = ME_AUTOSPACE;
me->remesh_voxel_size = 0.1f;
CustomData_reset(&me->vdata);
CustomData_reset(&me->edata);

View File

@ -0,0 +1,198 @@
/*
* 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) 2019 by Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bke
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <float.h>
#include <ctype.h>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "DNA_object_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_library.h"
#include "BKE_customdata.h"
#include "BKE_bvhutils.h"
#include "BKE_remesh.h"
#ifdef WITH_OPENVDB
# include "openvdb_capi.h"
#endif
#ifdef WITH_OPENVDB
struct OpenVDBLevelSet *BKE_remesh_voxel_ovdb_mesh_to_level_set_create(
Mesh *mesh, struct OpenVDBTransform *transform)
{
BKE_mesh_runtime_looptri_recalc(mesh);
const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(mesh);
MVertTri *verttri = MEM_callocN(sizeof(*verttri) * BKE_mesh_runtime_looptri_len(mesh),
"remesh_looptri");
BKE_mesh_runtime_verttri_from_looptri(
verttri, mesh->mloop, looptri, BKE_mesh_runtime_looptri_len(mesh));
unsigned int totfaces = BKE_mesh_runtime_looptri_len(mesh);
unsigned int totverts = mesh->totvert;
float *verts = (float *)MEM_malloc_arrayN(totverts * 3, sizeof(float), "remesh_input_verts");
unsigned int *faces = (unsigned int *)MEM_malloc_arrayN(
totfaces * 3, sizeof(unsigned int), "remesh_intput_faces");
for (unsigned int i = 0; i < totverts; i++) {
MVert *mvert = &mesh->mvert[i];
verts[i * 3] = mvert->co[0];
verts[i * 3 + 1] = mvert->co[1];
verts[i * 3 + 2] = mvert->co[2];
}
for (unsigned int i = 0; i < totfaces; i++) {
MVertTri *vt = &verttri[i];
faces[i * 3] = vt->tri[0];
faces[i * 3 + 1] = vt->tri[1];
faces[i * 3 + 2] = vt->tri[2];
}
struct OpenVDBLevelSet *level_set = OpenVDBLevelSet_create(false, NULL);
OpenVDBLevelSet_mesh_to_level_set(level_set, verts, faces, totverts, totfaces, transform);
MEM_freeN(verts);
MEM_freeN(faces);
MEM_freeN(verttri);
return level_set;
}
Mesh *BKE_remesh_voxel_ovdb_volume_to_mesh_nomain(struct OpenVDBLevelSet *level_set,
double isovalue,
double adaptivity,
bool relax_disoriented_triangles)
{
# ifdef WITH_OPENVDB
struct OpenVDBVolumeToMeshData output_mesh;
OpenVDBLevelSet_volume_to_mesh(
level_set, &output_mesh, isovalue, adaptivity, relax_disoriented_triangles);
# endif
Mesh *mesh = BKE_mesh_new_nomain(
output_mesh.totvertices, 0, output_mesh.totquads + output_mesh.tottriangles, 0, 0);
int q = output_mesh.totquads;
for (int i = 0; i < output_mesh.totvertices; i++) {
float vco[3] = {output_mesh.vertices[i * 3],
output_mesh.vertices[i * 3 + 1],
output_mesh.vertices[i * 3 + 2]};
copy_v3_v3(mesh->mvert[i].co, vco);
}
for (int i = 0; i < output_mesh.totquads; i++) {
mesh->mface[i].v4 = output_mesh.quads[i * 4];
mesh->mface[i].v3 = output_mesh.quads[i * 4 + 1];
mesh->mface[i].v2 = output_mesh.quads[i * 4 + 2];
mesh->mface[i].v1 = output_mesh.quads[i * 4 + 3];
}
for (int i = 0; i < output_mesh.tottriangles; i++) {
mesh->mface[i + q].v4 = 0;
mesh->mface[i + q].v3 = output_mesh.triangles[i * 3];
mesh->mface[i + q].v2 = output_mesh.triangles[i * 3 + 1];
mesh->mface[i + q].v1 = output_mesh.triangles[i * 3 + 2];
}
BKE_mesh_calc_edges_tessface(mesh);
BKE_mesh_convert_mfaces_to_mpolys(mesh);
BKE_mesh_tessface_clear(mesh);
BKE_mesh_calc_normals(mesh);
MEM_freeN(output_mesh.quads);
MEM_freeN(output_mesh.vertices);
if (output_mesh.tottriangles > 0) {
MEM_freeN(output_mesh.triangles);
}
return mesh;
}
#endif
Mesh *BKE_remesh_voxel_to_mesh_nomain(Mesh *mesh, float voxel_size)
{
Mesh *new_mesh = NULL;
#ifdef WITH_OPENVDB
struct OpenVDBLevelSet *level_set;
struct OpenVDBTransform *xform = OpenVDBTransform_create();
OpenVDBTransform_create_linear_transform(xform, (double)voxel_size);
level_set = BKE_remesh_voxel_ovdb_mesh_to_level_set_create(mesh, xform);
new_mesh = BKE_remesh_voxel_ovdb_volume_to_mesh_nomain(level_set, 0.0, 0.0, false);
OpenVDBLevelSet_free(level_set);
OpenVDBTransform_free(xform);
#endif
return new_mesh;
}
void BKE_remesh_reproject_paint_mask(Mesh *target, Mesh *source)
{
BVHTreeFromMesh bvhtree = {
.nearest_callback = NULL,
};
BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2);
MVert *target_verts = CustomData_get_layer(&target->vdata, CD_MVERT);
float *target_mask;
if (CustomData_has_layer(&target->vdata, CD_PAINT_MASK)) {
target_mask = CustomData_get_layer(&target->vdata, CD_PAINT_MASK);
}
else {
target_mask = CustomData_add_layer(
&target->vdata, CD_PAINT_MASK, CD_CALLOC, NULL, target->totvert);
}
float *source_mask;
if (CustomData_has_layer(&source->vdata, CD_PAINT_MASK)) {
source_mask = CustomData_get_layer(&source->vdata, CD_PAINT_MASK);
}
else {
source_mask = CustomData_add_layer(
&source->vdata, CD_PAINT_MASK, CD_CALLOC, NULL, source->totvert);
}
for (int i = 0; i < target->totvert; i++) {
float from_co[3];
BVHTreeNearest nearest;
nearest.index = -1;
nearest.dist_sq = FLT_MAX;
copy_v3_v3(from_co, target_verts[i].co);
BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
if (nearest.index != -1) {
target_mask[i] = source_mask[nearest.index];
}
}
free_bvhtree_from_mesh(&bvhtree);
}

View File

@ -3752,5 +3752,10 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
}
for (Mesh *mesh = bmain->meshes.first; mesh; mesh = mesh->id.next) {
if (mesh->remesh_voxel_size == 0.0f) {
mesh->remesh_voxel_size = 0.1f;
}
}
}
}

View File

@ -45,4 +45,7 @@ bool ED_sculpt_mask_box_select(struct bContext *C,
/* sculpt_undo.c */
void ED_sculpt_undosys_type(struct UndoType *ut);
void ED_sculpt_undo_geometry_begin(struct Object *ob);
void ED_sculpt_undo_geometry_end(struct Object *ob);
#endif /* __ED_SCULPT_H__ */

View File

@ -57,6 +57,7 @@ set(SRC
object_ops.c
object_random.c
object_relations.c
object_remesh.c
object_select.c
object_shader_fx.c
object_shapekey.c

View File

@ -279,6 +279,9 @@ void OBJECT_OT_bake(wmOperatorType *ot);
/* object_random.c */
void TRANSFORM_OT_vertex_random(struct wmOperatorType *ot);
/* object_remesh.c */
void OBJECT_OT_voxel_remesh(struct wmOperatorType *ot);
/* object_transfer_data.c */
void OBJECT_OT_data_transfer(struct wmOperatorType *ot);
void OBJECT_OT_datalayout_transfer(struct wmOperatorType *ot);

View File

@ -257,6 +257,8 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_hide_view_clear);
WM_operatortype_append(OBJECT_OT_hide_view_set);
WM_operatortype_append(OBJECT_OT_hide_collection);
WM_operatortype_append(OBJECT_OT_voxel_remesh);
}
void ED_operatormacros_object(void)

View File

@ -0,0 +1,159 @@
/*
* 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) 2019 by Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup edobj
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <ctype.h>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "DNA_scene_types.h"
#include "DNA_object_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_mesh_types.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_customdata.h"
#include "BKE_remesh.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "ED_mesh.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_undo.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "WM_api.h"
#include "WM_types.h"
#include "WM_message.h"
#include "WM_toolsystem.h"
#include "object_intern.h" // own include
static bool object_remesh_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
if (BKE_object_is_in_editmode(ob)) {
CTX_wm_operator_poll_msg_set(C, "The voxel remesher cannot run from edit mode.");
return false;
}
if (ob->mode == OB_MODE_SCULPT && ob->sculpt->bm) {
CTX_wm_operator_poll_msg_set(C, "The voxel remesher cannot run with dyntopo activated.");
}
return ED_operator_object_active_editable_mesh(C);
}
static int voxel_remesh_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
Main *bmain = CTX_data_main(C);
Mesh *mesh = ob->data;
Mesh *new_mesh;
if (mesh->remesh_voxel_size <= 0.0f) {
BKE_report(op->reports, RPT_ERROR, "Voxel remesher cannot run with a voxel size of 0.0.");
return OPERATOR_CANCELLED;
}
if (ob->mode == OB_MODE_SCULPT) {
ED_sculpt_undo_geometry_begin(ob);
}
new_mesh = BKE_remesh_voxel_to_mesh_nomain(mesh, mesh->remesh_voxel_size);
if (!new_mesh) {
return OPERATOR_CANCELLED;
}
Mesh *obj_mesh_copy;
if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) {
obj_mesh_copy = BKE_mesh_new_nomain_from_template(mesh, mesh->totvert, 0, 0, 0, 0);
CustomData_copy(
&mesh->vdata, &obj_mesh_copy->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, mesh->totvert);
for (int i = 0; i < mesh->totvert; i++) {
copy_v3_v3(obj_mesh_copy->mvert[i].co, mesh->mvert[i].co);
}
}
BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true);
BKE_mesh_free(new_mesh);
if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) {
BKE_remesh_reproject_paint_mask(mesh, obj_mesh_copy);
BKE_mesh_free(obj_mesh_copy);
}
if (mesh->flag & ME_REMESH_SMOOTH_NORMALS) {
BKE_mesh_smooth_flag_set(ob, true);
}
if (ob->mode == OB_MODE_SCULPT) {
ED_sculpt_undo_geometry_end(ob);
}
BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
return OPERATOR_FINISHED;
}
void OBJECT_OT_voxel_remesh(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Voxel Remesh";
ot->description =
"Calculates a new manifold mesh based on the volume of the current mesh. All data layers "
"will be lost";
ot->idname = "OBJECT_OT_voxel_remesh";
/* api callbacks */
ot->poll = object_remesh_poll;
ot->exec = voxel_remesh_exec;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

View File

@ -5695,31 +5695,19 @@ static void sculpt_dynamic_topology_disable_ex(
CustomData_free(&me->pdata, me->totpoly);
/* Copy over stored custom data */
me->totvert = unode->bm_enter_totvert;
me->totloop = unode->bm_enter_totloop;
me->totpoly = unode->bm_enter_totpoly;
me->totedge = unode->bm_enter_totedge;
me->totvert = unode->geom_totvert;
me->totloop = unode->geom_totloop;
me->totpoly = unode->geom_totpoly;
me->totedge = unode->geom_totedge;
me->totface = 0;
CustomData_copy(&unode->bm_enter_vdata,
&me->vdata,
CD_MASK_MESH.vmask,
CD_DUPLICATE,
unode->bm_enter_totvert);
CustomData_copy(&unode->bm_enter_edata,
&me->edata,
CD_MASK_MESH.emask,
CD_DUPLICATE,
unode->bm_enter_totedge);
CustomData_copy(&unode->bm_enter_ldata,
&me->ldata,
CD_MASK_MESH.lmask,
CD_DUPLICATE,
unode->bm_enter_totloop);
CustomData_copy(&unode->bm_enter_pdata,
&me->pdata,
CD_MASK_MESH.pmask,
CD_DUPLICATE,
unode->bm_enter_totpoly);
CustomData_copy(
&unode->geom_vdata, &me->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, unode->geom_totvert);
CustomData_copy(
&unode->geom_edata, &me->edata, CD_MASK_MESH.emask, CD_DUPLICATE, unode->geom_totedge);
CustomData_copy(
&unode->geom_ldata, &me->ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, unode->geom_totloop);
CustomData_copy(
&unode->geom_pdata, &me->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, unode->geom_totpoly);
BKE_mesh_update_customdata_pointers(me, false);
}

View File

@ -63,6 +63,7 @@ typedef enum {
SCULPT_UNDO_DYNTOPO_BEGIN,
SCULPT_UNDO_DYNTOPO_END,
SCULPT_UNDO_DYNTOPO_SYMMETRIZE,
SCULPT_UNDO_GEOMETRY,
} SculptUndoType;
typedef struct SculptUndoNode {
@ -94,18 +95,20 @@ typedef struct SculptUndoNode {
/* bmesh */
struct BMLogEntry *bm_entry;
bool applied;
CustomData bm_enter_vdata;
CustomData bm_enter_edata;
CustomData bm_enter_ldata;
CustomData bm_enter_pdata;
int bm_enter_totvert;
int bm_enter_totedge;
int bm_enter_totloop;
int bm_enter_totpoly;
/* shape keys */
char shapeName[sizeof(((KeyBlock *)0))->name];
/* geometry modification operations and bmesh enter data */
CustomData geom_vdata;
CustomData geom_edata;
CustomData geom_ldata;
CustomData geom_pdata;
int geom_totvert;
int geom_totedge;
int geom_totloop;
int geom_totpoly;
size_t undo_size;
} SculptUndoNode;

View File

@ -44,6 +44,7 @@
#include "BKE_ccg.h"
#include "BKE_context.h"
#include "BKE_customdata.h"
#include "BKE_multires.h"
#include "BKE_paint.h"
#include "BKE_key.h"
@ -425,6 +426,32 @@ static void sculpt_undo_bmesh_restore_end(bContext *C,
}
}
static void sculpt_undo_geometry_restore(SculptUndoNode *unode, Object *ob)
{
Mesh *me;
sculpt_pbvh_clear(ob);
me = ob->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);
me->totvert = unode->geom_totvert;
me->totedge = unode->geom_totedge;
me->totloop = unode->geom_totloop;
me->totpoly = unode->geom_totpoly;
me->totface = 0;
CustomData_copy(
&unode->geom_vdata, &me->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, unode->geom_totvert);
CustomData_copy(
&unode->geom_edata, &me->edata, CD_MASK_MESH.emask, CD_DUPLICATE, unode->geom_totedge);
CustomData_copy(
&unode->geom_ldata, &me->ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, unode->geom_totloop);
CustomData_copy(
&unode->geom_pdata, &me->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, unode->geom_totpoly);
BKE_mesh_update_customdata_pointers(me, false);
}
/* Handle all dynamic-topology updates
*
* Returns true if this was a dynamic-topology undo step, otherwise
@ -442,7 +469,6 @@ static int sculpt_undo_bmesh_restore(bContext *C,
case SCULPT_UNDO_DYNTOPO_END:
sculpt_undo_bmesh_restore_end(C, unode, ob, ss);
return true;
default:
if (ss->bm_log) {
sculpt_undo_bmesh_restore_generic(C, unode, ob, ss);
@ -480,6 +506,24 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
if (lb->first) {
unode = lb->first;
if (unode->type == SCULPT_UNDO_GEOMETRY) {
if (unode->applied) {
sculpt_undo_geometry_restore(unode->next, ob);
unode->next->applied = true;
unode->applied = false;
}
else {
sculpt_undo_geometry_restore(unode, ob);
unode->next->applied = false;
unode->applied = true;
}
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
return;
}
}
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
if (lb->first && sculpt_undo_bmesh_restore(C, lb->first, ob, ss)) {
@ -487,6 +531,7 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
}
for (unode = lb->first; unode; unode = unode->next) {
if (!STREQ(unode->idname, ob->id.name)) {
continue;
}
@ -530,6 +575,8 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
BLI_assert(!"Dynamic topology should've already been handled");
break;
case SCULPT_UNDO_GEOMETRY:
break;
}
}
@ -617,17 +664,17 @@ static void sculpt_undo_free_list(ListBase *lb)
BM_log_entry_drop(unode->bm_entry);
}
if (unode->bm_enter_totvert) {
CustomData_free(&unode->bm_enter_vdata, unode->bm_enter_totvert);
if (unode->geom_totvert) {
CustomData_free(&unode->geom_vdata, unode->geom_totvert);
}
if (unode->bm_enter_totedge) {
CustomData_free(&unode->bm_enter_edata, unode->bm_enter_totedge);
if (unode->geom_totedge) {
CustomData_free(&unode->geom_edata, unode->geom_totedge);
}
if (unode->bm_enter_totloop) {
CustomData_free(&unode->bm_enter_ldata, unode->bm_enter_totloop);
if (unode->geom_totloop) {
CustomData_free(&unode->geom_ldata, unode->geom_totloop);
}
if (unode->bm_enter_totpoly) {
CustomData_free(&unode->bm_enter_pdata, unode->bm_enter_totpoly);
if (unode->geom_totpoly) {
CustomData_free(&unode->geom_pdata, unode->geom_totpoly);
}
MEM_freeN(unode);
@ -743,6 +790,7 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt
case SCULPT_UNDO_DYNTOPO_END:
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
BLI_assert(!"Dynamic topology should've already been handled");
case SCULPT_UNDO_GEOMETRY:
break;
}
@ -824,6 +872,36 @@ static void sculpt_undo_store_mask(Object *ob, SculptUndoNode *unode)
BKE_pbvh_vertex_iter_end;
}
static SculptUndoNode *sculpt_undo_geometry_push(Object *ob, SculptUndoType type)
{
UndoSculpt *usculpt = sculpt_undo_get_nodes();
Mesh *me = ob->data;
bool applied;
SculptUndoNode *unode = usculpt->nodes.first;
/* Store the original mesh in the first node, modifications in the second */
applied = unode != NULL;
unode = MEM_callocN(sizeof(*unode), __func__);
BLI_strncpy(unode->idname, ob->id.name, sizeof(unode->idname));
unode->type = type;
unode->applied = applied;
CustomData_copy(&me->vdata, &unode->geom_vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, me->totvert);
CustomData_copy(&me->edata, &unode->geom_edata, CD_MASK_MESH.emask, CD_DUPLICATE, me->totedge);
CustomData_copy(&me->ldata, &unode->geom_ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, me->totloop);
CustomData_copy(&me->pdata, &unode->geom_pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, me->totpoly);
unode->geom_totvert = me->totvert;
unode->geom_totedge = me->totedge;
unode->geom_totloop = me->totloop;
unode->geom_totpoly = me->totpoly;
BLI_addtail(&usculpt->nodes, unode);
return unode;
}
static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, SculptUndoType type)
{
UndoSculpt *usculpt = sculpt_undo_get_nodes();
@ -852,17 +930,17 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
* (converting polys to triangles) that the BMLog can't
* fully restore from */
CustomData_copy(
&me->vdata, &unode->bm_enter_vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, me->totvert);
&me->vdata, &unode->geom_vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, me->totvert);
CustomData_copy(
&me->edata, &unode->bm_enter_edata, CD_MASK_MESH.emask, CD_DUPLICATE, me->totedge);
&me->edata, &unode->geom_edata, CD_MASK_MESH.emask, CD_DUPLICATE, me->totedge);
CustomData_copy(
&me->ldata, &unode->bm_enter_ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, me->totloop);
&me->ldata, &unode->geom_ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, me->totloop);
CustomData_copy(
&me->pdata, &unode->bm_enter_pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, me->totpoly);
unode->bm_enter_totvert = me->totvert;
unode->bm_enter_totedge = me->totedge;
unode->bm_enter_totloop = me->totloop;
unode->bm_enter_totpoly = me->totpoly;
&me->pdata, &unode->geom_pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, me->totpoly);
unode->geom_totvert = me->totvert;
unode->geom_totedge = me->totedge;
unode->geom_totloop = me->totloop;
unode->geom_totpoly = me->totpoly;
unode->bm_entry = BM_log_entry_add(ss->bm_log);
BM_log_all_added(ss->bm, ss->bm_log);
@ -906,6 +984,7 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
case SCULPT_UNDO_DYNTOPO_BEGIN:
case SCULPT_UNDO_DYNTOPO_END:
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
case SCULPT_UNDO_GEOMETRY:
break;
}
}
@ -928,6 +1007,11 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
BLI_thread_unlock(LOCK_CUSTOM1);
return unode;
}
else if (type == SCULPT_UNDO_GEOMETRY) {
unode = sculpt_undo_geometry_push(ob, type);
BLI_thread_unlock(LOCK_CUSTOM1);
return unode;
}
else if ((unode = sculpt_undo_get_node(node))) {
BLI_thread_unlock(LOCK_CUSTOM1);
return unode;
@ -967,6 +1051,7 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
case SCULPT_UNDO_DYNTOPO_END:
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
BLI_assert(!"Dynamic topology should've already been handled");
case SCULPT_UNDO_GEOMETRY:
break;
}
@ -1163,6 +1248,18 @@ static void sculpt_undosys_step_free(UndoStep *us_p)
sculpt_undo_free_list(&us->data.nodes);
}
void ED_sculpt_undo_geometry_begin(struct Object *ob)
{
sculpt_undo_push_begin("voxel remesh");
sculpt_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY);
}
void ED_sculpt_undo_geometry_end(struct Object *ob)
{
sculpt_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY);
sculpt_undo_push_end();
}
/* Export for ED_undo_sys. */
void ED_sculpt_undosys_type(UndoType *ut)
{

View File

@ -193,9 +193,10 @@ typedef struct Mesh {
short totcol;
float remesh_voxel_size;
char _pad1[4];
/** Deprecated multiresolution modeling data, only keep for loading old files. */
struct Multires *mr DNA_DEPRECATED;
void *_pad1;
Mesh_Runtime runtime;
} Mesh;
@ -250,6 +251,8 @@ enum {
ME_FLAG_UNUSED_8 = 1 << 8, /* cleared */
ME_DS_EXPAND = 1 << 9,
ME_SCULPT_DYNAMIC_TOPOLOGY = 1 << 10,
ME_REMESH_SMOOTH_NORMALS = 1 << 11,
ME_REMESH_REPROJECT_PAINT_MASK = 1 << 12,
};
/* me->cd_flag */

View File

@ -2998,6 +2998,30 @@ static void rna_def_mesh(BlenderRNA *brna)
rna_def_paint_mask(brna, prop);
/* End paint mask */
/* Remesh */
prop = RNA_def_property(srna, "remesh_voxel_size", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, NULL, "remesh_voxel_size");
RNA_def_property_float_default(prop, 0.1f);
RNA_def_property_range(prop, 0.00001f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0001f, FLT_MAX, 0.01, 4);
RNA_def_property_ui_text(prop,
"Voxel size",
"Size of the voxel in object space used for volume evaluation. Lower "
"values preserve finer details");
RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
prop = RNA_def_property(srna, "remesh_smooth_normals", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_SMOOTH_NORMALS);
RNA_def_property_ui_text(prop, "Smooth normals", "Smooth the normals of the remesher result");
RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
prop = RNA_def_property(srna, "remesh_preserve_paint_mask", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_REPROJECT_PAINT_MASK);
RNA_def_property_boolean_default(prop, false);
RNA_def_property_ui_text(prop, "Preserve Paint Mask", "Keep the current mask on the new mesh");
RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
/* End remesh */
prop = RNA_def_property(srna, "use_auto_smooth", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_AUTOSMOOTH);
RNA_def_property_ui_text(