Patch T41093: Cleanup non-manifold

by Caretdashcaret with own edits
This commit is contained in:
Campbell Barton 2014-08-11 09:20:11 +10:00
parent 544a5a6d80
commit a88a2e6460
Notes: blender-bot 2023-02-14 20:05:22 +01:00
Referenced by issue #41093, Add a new cleanup to 3D print tools
3 changed files with 157 additions and 0 deletions

View File

@ -133,6 +133,7 @@ classes = (
operators.Print3DCleanIsolated,
operators.Print3DCleanDistorted,
operators.Print3DCleanThin,
operators.Print3DCleanNonManifold,
operators.Print3DSelectReport,

View File

@ -433,6 +433,160 @@ class Print3DCleanDistorted(Operator):
return {'CANCELLED'}
class Print3DCleanNonManifold(Operator):
"""Cleanup problems, like holes, non-manifold vertices, and inverted normals"""
bl_idname = "mesh.print3d_clean_non_manifold"
bl_label = "Print3D Clean Non-Manifold and Inverted"
bl_options = {'REGISTER', 'UNDO'}
threshold = bpy.props.FloatProperty(
name="threshold",
description="Minimum distance between elements to merge",
default=0.0001,
)
sides = bpy.props.IntProperty(
name="sides",
description="Number of sides in hole required to fill",
default=4,
)
def execute(self, context):
self.context = context
mode_orig = context.mode
self.setup_environment()
bm_key_orig = self.elem_count(context)
self.delete_loose()
self.remove_doubles(self.threshold)
self.dissolve_degenerate(self.threshold)
# may take a while
self.fix_non_manifold(context, self.sides)
self.make_normals_consistently_outwards()
bm_key = self.elem_count(context)
if mode_orig != 'EDIT_MESH':
bpy.ops.object.mode_set(mode='OBJECT')
self.report(
{'INFO'},
"Modified Verts:%+d, Edges:%+d, Faces:%+d" %
(bm_key[0] - bm_key_orig[0],
bm_key[1] - bm_key_orig[1],
bm_key[2] - bm_key_orig[2],
))
return {'FINISHED'}
@staticmethod
def elem_count(context):
bm = bmesh.from_edit_mesh(context.edit_object.data)
return len(bm.verts), len(bm.edges), len(bm.faces)
@staticmethod
def setup_environment():
"""set the mode as edit, select mode as vertices, and reveal hidden vertices"""
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='VERT')
bpy.ops.mesh.reveal()
@staticmethod
def remove_doubles(threshold):
"""remove duplicate vertices"""
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold=threshold)
@staticmethod
def delete_loose():
"""delete loose vertices/edges/faces"""
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete_loose()
@staticmethod
def dissolve_degenerate(threshold):
"""dissolve zero area faces and zero length edges"""
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.dissolve_degenerate(threshold=threshold)
@staticmethod
def make_normals_consistently_outwards():
"""have all normals face outwards"""
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.normals_make_consistent()
@classmethod
def fix_non_manifold(cls, context, sides):
"""naive iterate-until-no-more approach for fixing manifolds"""
total_non_manifold = cls.count_non_manifold_verts(context)
if not total_non_manifold:
return
bm_states = set()
bm_key = cls.elem_count(context)
bm_states.add(bm_key)
while True:
cls.fill_non_manifold(sides)
cls.delete_newly_generated_non_manifold_verts()
bm_key = cls.elem_count(context)
if bm_key in bm_states:
break
else:
bm_states.add(bm_key)
@staticmethod
def select_non_manifold_verts(
use_wire=False,
use_boundary=False,
use_multi_face=False,
use_non_contiguous=False,
use_verts=False,
):
"""select non-manifold vertices"""
bpy.ops.mesh.select_non_manifold(
extend=False,
use_wire=use_wire,
use_boundary=use_boundary,
use_multi_face=use_multi_face,
use_non_contiguous=use_non_contiguous,
use_verts=use_verts,
)
@classmethod
def count_non_manifold_verts(cls, context):
"""return a set of coordinates of non-manifold vertices"""
cls.select_non_manifold_verts(
use_wire=True,
use_boundary=True,
use_verts=True,
)
bm = bmesh.from_edit_mesh(context.edit_object.data)
return sum((1 for v in bm.verts if v.select))
@classmethod
def fill_non_manifold(cls, sides):
"""fill holes and then fill in any remnant non-manifolds"""
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.fill_holes(sides=sides)
# fill selected edge faces, which could be additional holes
cls.select_non_manifold_verts(use_boundary=True)
bpy.ops.mesh.fill()
@classmethod
def delete_newly_generated_non_manifold_verts(cls):
"""delete any newly generated vertices from the filling repair"""
cls.select_non_manifold_verts(use_wire=True, use_verts=True)
bpy.ops.mesh.delete(type='VERT')
class Print3DCleanThin(Operator):
"""Ensure minimum thickness"""
bl_idname = "mesh.print3d_clean_thin"

View File

@ -105,6 +105,8 @@ class Print3DToolBar:
rowsub = col.row(align=True)
rowsub.operator("mesh.print3d_clean_distorted", text="Distorted")
rowsub.prop(print_3d, "angle_distort", text="")
col = layout.column()
col.operator("mesh.print3d_clean_non_manifold", text="Non-Manifold")
# XXX TODO
# col.operator("mesh.print3d_clean_thin", text="Wall Thickness")