From a88a2e646018b3f9b8d1f818f0a08370b2a0dd67 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 11 Aug 2014 09:20:11 +1000 Subject: Patch T41093: Cleanup non-manifold by Caretdashcaret with own edits --- object_print3d_utils/operators.py | 154 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) (limited to 'object_print3d_utils/operators.py') diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py index 4f9bc647..ec12b0d9 100644 --- a/object_print3d_utils/operators.py +++ b/object_print3d_utils/operators.py @@ -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" -- cgit v1.2.3