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:
parent
16c28b5a67
commit
45a45f7d66
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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__ */
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue