Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2014-08-11 03:20:11 +0400
committerCampbell Barton <ideasman42@gmail.com>2014-08-11 03:20:11 +0400
commita88a2e646018b3f9b8d1f818f0a08370b2a0dd67 (patch)
tree99867a14c1a285a261f30b9f2e094d10798c9026 /object_print3d_utils/operators.py
parent544a5a6d80ca167cac3e8b5c47aad8bdd20eaf5e (diff)
Patch T41093: Cleanup non-manifold
by Caretdashcaret with own edits
Diffstat (limited to 'object_print3d_utils/operators.py')
-rw-r--r--object_print3d_utils/operators.py154
1 files changed, 154 insertions, 0 deletions
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"