[Bf-extensions-cvs] [03d3376] dxf_import: Patch T41093: Cleanup non-manifold

Campbell Barton noreply at git.blender.org
Fri Aug 15 12:38:29 CEST 2014


Commit: 03d3376e7e73efcf6cbb188079658dd69d01e8f0
Author: Campbell Barton
Date:   Mon Aug 11 09:20:11 2014 +1000
Branches: dxf_import
https://developer.blender.org/rBA03d3376e7e73efcf6cbb188079658dd69d01e8f0

Patch T41093: Cleanup non-manifold

by Caretdashcaret with own edits

===================================================================

M	object_print3d_utils/__init__.py
M	object_print3d_utils/operators.py
M	object_print3d_utils/ui.py

===================================================================

diff --git a/object_print3d_utils/__init__.py b/object_print3d_utils/__init__.py
index 889c65d..5c5d341 100644
--- a/object_print3d_utils/__init__.py
+++ b/object_print3d_utils/__init__.py
@@ -133,6 +133,7 @@ classes = (
     operators.Print3DCleanIsolated,
     operators.Print3DCleanDistorted,
     operators.Print3DCleanThin,
+    operators.Print3DCleanNonManifold,
 
     operators.Print3DSelectReport,
 
diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py
index 4f9bc64..ec12b0d 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"
diff --git a/object_print3d_utils/ui.py b/object_print3d_utils/ui.py
index 508f8ef..dfe2ac4 100644
--- a/object_print3d_utils/ui.py
+++ b/object_print3d_utils/ui.py
@@ -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")



More information about the Bf-extensions-cvs mailing list