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:
-rw-r--r--object_print3d_utils/__init__.py1
-rw-r--r--object_print3d_utils/operators.py154
-rw-r--r--object_print3d_utils/ui.py2
3 files changed, 157 insertions, 0 deletions
diff --git a/object_print3d_utils/__init__.py b/object_print3d_utils/__init__.py
index 889c65dc..5c5d341e 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 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"
diff --git a/object_print3d_utils/ui.py b/object_print3d_utils/ui.py
index 508f8efa..dfe2ac49 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")