mesh tools: add cut faces to ui
authormeta-androcto <meta.androcto1@gmail.com>
Thu, 6 Jun 2019 00:52:37 +0000 (10:52 +1000)
committermeta-androcto <meta.androcto1@gmail.com>
Thu, 6 Jun 2019 00:52:37 +0000 (10:52 +1000)
mesh_tools/__init__.py
mesh_tools/mesh_cut_faces.py

index 9aacaaf..d6f4bac 100644 (file)
@@ -48,6 +48,7 @@ if "bpy" in locals():
     importlib.reload(mesh_edges_floor_plan)
     importlib.reload(mesh_edges_length)
     importlib.reload(pkhg_faces)
+    importlib.reload(mesh_cut_faces)
 
 else:
     from . import mesh_offset_edges
@@ -61,6 +62,7 @@ else:
     from . import mesh_edges_floor_plan
     from . import mesh_edges_length
     from . import pkhg_faces
+    from . import mesh_cut_faces
 
 
 import bmesh
@@ -970,6 +972,9 @@ class VIEW3D_PT_edit_mesh_tools(Panel):
             row.operator("mesh.face_inset_fillet",
                             text="Face Inset Fillet")
             row = col_top.row(align=True)
+            row.operator("mesh.ext_cut_faces",
+                            text="Cut Faces")
+            row = col_top.row(align=True)
             row.operator("mesh.extrude_reshape",
                             text="Push/Pull Faces")
             row = col_top.row(align=True)
@@ -991,7 +996,7 @@ class VIEW3D_PT_edit_mesh_tools(Panel):
         else:
             split.prop(et, "display_util", text="Utility Tools", icon='RIGHTARROW')
         split.menu("VIEW3D_MT_Edit_MultiMET", text="", icon='RESTRICT_SELECT_OFF')
-        # face - settings
+        # util - settings
         if et.display_util:
             box = col.column(align=True).box().column()
             col_top = box.column(align=True)
@@ -1128,6 +1133,7 @@ def register():
     mesh_edges_floor_plan.register()
     mesh_edges_length.register()
     pkhg_faces.register()
+    mesh_cut_faces.register()
 
 
 # unregistering and removing menus
@@ -1152,6 +1158,8 @@ def unregister():
     mesh_edges_floor_plan.unregister()
     mesh_edges_length.unregister()
     pkhg_faces.unregister()
+    mesh_cut_faces.unregister()
+
 
 if __name__ == "__main__":
     register()
index 1522b15..a5297c9 100644 (file)
@@ -1,22 +1,13 @@
-# gpl author: Stanislav Blinov
-
 bl_info = {
-    "name": "Cut Faces",
-    "author": "Stanislav Blinov",
-    "version": (1, 0, 0),
-    "blender": (2, 72, 0),
-    "description": "Cut Faces and Deselect Boundary operators",
-    "category": "Mesh", }
+    "name" : "Cut Faces",
+    "author" : "Stanislav Blinov",
+    "version" : (1, 0, 0),
+    "blender" : (2, 80, 0),
+    "description" : "Cut Faces and Deselect Boundary operators",
+    "category" : "Mesh",}
 
 import bpy
 import bmesh
-from bpy.types import Operator
-from bpy.props import (
-        BoolProperty,
-        IntProperty,
-        EnumProperty,
-        )
-
 
 def bmesh_from_object(object):
     mesh = object.data
@@ -27,7 +18,6 @@ def bmesh_from_object(object):
         bm.from_mesh(mesh)
     return bm
 
-
 def bmesh_release(bm, object):
     mesh = object.data
     bm.select_flush_mode()
@@ -37,7 +27,6 @@ def bmesh_release(bm, object):
         bm.to_mesh(mesh)
         bm.free()
 
-
 def calc_face(face, keep_caps=True):
 
     assert face.tag
@@ -80,49 +69,41 @@ def calc_face(face, keep_caps=True):
 
     return result
 
-
 def get_edge_rings(bm, keep_caps=True):
 
     def tag_face(face):
         if face.select:
             face.tag = True
-            for edge in face.edges:
-                edge.tag = False
+            for edge in face.edges: edge.tag = False
         return face.select
 
     # fetch selected faces while setting up tags
-    selected_faces = [f for f in bm.faces if tag_face(f)]
+    selected_faces = [ f for f in bm.faces if tag_face(f) ]
 
     edges = []
 
     try:
         # generate a list of edges to select:
         # traversing only tagged faces, since calc_face can walk and untag islands
-        for face in filter(lambda f: f.tag, selected_faces):
-            edges += calc_face(face, keep_caps)
+        for face in filter(lambda f: f.tag, selected_faces): edges += calc_face(face, keep_caps)
     finally:
         # housekeeping: clear tags
         for face in selected_faces:
             face.tag = False
-            for edge in face.edges:
-                edge.tag = False
+            for edge in face.edges: edge.tag = False
 
     return edges
 
-
-class MESH_xOT_deselect_boundary(Operator):
+class MESH_xOT_deselect_boundary(bpy.types.Operator):
+    """Deselect boundary edges of selected faces"""
     bl_idname = "mesh.ext_deselect_boundary"
     bl_label = "Deselect Boundary"
-    bl_description = ("Deselect boundary edges of selected faces\n"
-                      "Note: if all Faces are selected there is no boundary,\n"
-                      "so the tool will not have results")
     bl_options = {'REGISTER', 'UNDO'}
 
-    keep_cap_edges: BoolProperty(
-                        name="Keep Cap Edges",
-                        description="Keep quad strip cap edges selected",
-                        default=False
-                        )
+    keep_cap_edges: bpy.props.BoolProperty(
+        name        = "Keep Cap Edges",
+        description = "Keep quad strip cap edges selected",
+        default     = False)
 
     @classmethod
     def poll(cls, context):
@@ -134,9 +115,9 @@ class MESH_xOT_deselect_boundary(Operator):
         bm = bmesh_from_object(object)
 
         try:
-            edges = get_edge_rings(bm, keep_caps=self.keep_cap_edges)
+            edges = get_edge_rings(bm, keep_caps = self.keep_cap_edges)
             if not edges:
-                self.report({'WARNING'}, "No suitable Face selection found. Operation cancelled")
+                self.report({'WARNING'}, "No suitable selection found")
                 return {'CANCELLED'}
 
             bpy.ops.mesh.select_all(action='DESELECT')
@@ -151,83 +132,67 @@ class MESH_xOT_deselect_boundary(Operator):
 
         return {'FINISHED'}
 
-
-class MESH_xOT_cut_faces(Operator):
+class MESH_xOT_cut_faces(bpy.types.Operator):
+    """Cut selected faces, connecting through their adjacent edges"""
     bl_idname = "mesh.ext_cut_faces"
     bl_label = "Cut Faces"
-    bl_description = "Cut selected faces, connected through their adjacent edges"
     bl_options = {'REGISTER', 'UNDO'}
 
     # from bmesh_operators.h
-    SUBD_INNERVERT = 0
-    SUBD_PATH = 1
-    SUBD_FAN = 2
-    SUBD_STRAIGHT_CUT = 3
-
-    num_cuts: IntProperty(
-            name="Number of Cuts",
-            default=1,
-            min=1,
-            max=100,
-            subtype='UNSIGNED'
-            )
-    use_single_edge: BoolProperty(
-            name="Quad/Tri Mode",
-            description="Cut boundary faces",
-            default=False
-            )
-    corner_type: EnumProperty(
-            items=[('SUBD_INNERVERT', "Inner Vert", ""),
-                   ('SUBD_PATH', "Path", ""),
-                   ('SUBD_FAN', "Fan", ""),
-                   ('SUBD_STRAIGHT_CUT', "Straight Cut", ""),
-                   ],
-            name="Quad Corner Type",
-            description="How to subdivide quad corners",
-            default='SUBD_STRAIGHT_CUT'
-            )
-    use_grid_fill: BoolProperty(
-            name="Use Grid Fill",
-            description="Fill fully enclosed faces with a grid",
-            default=True
-            )
+    INNERVERT    = 0
+    PATH         = 1
+    FAN          = 2
+    STRAIGHT_CUT = 3
+
+    num_cuts: bpy.props.IntProperty(
+        name    = "Number of Cuts",
+        default = 1,
+        min     = 1,
+        max     = 100,
+        subtype = 'UNSIGNED')
+
+    use_single_edge: bpy.props.BoolProperty(
+        name        = "Quad/Tri Mode",
+        description = "Cut boundary faces",
+        default     = False)
+
+    corner_type: bpy.props.EnumProperty(
+        items = [('INNER_VERT', "Inner Vert", ""),
+                 ('PATH', "Path", ""),
+                 ('FAN', "Fan", ""),
+                 ('STRAIGHT_CUT', "Straight Cut", ""),],
+        name = "Quad Corner Type",
+        description = "How to subdivide quad corners",
+        default = 'STRAIGHT_CUT')
+
+    use_grid_fill: bpy.props.BoolProperty(
+        name        = "Use Grid Fill",
+        description = "Fill fully enclosed faces with a grid",
+        default     = True)
 
     @classmethod
     def poll(cls, context):
         active_object = context.active_object
         return active_object and active_object.type == 'MESH' and active_object.mode == 'EDIT'
 
-    def draw(self, context):
-        layout = self.layout
-
-        layout.label(text="Number of Cuts:")
-        layout.prop(self, "num_cuts", text="")
-
-        layout.prop(self, "use_single_edge")
-        layout.prop(self, "use_grid_fill")
-
-        layout.label(text="Quad Corner Type:")
-        layout.prop(self, "corner_type", text="")
-
     def cut_edges(self, context):
         object = context.active_object
         bm = bmesh_from_object(object)
 
         try:
-            edges = get_edge_rings(bm, keep_caps=True)
+            edges = get_edge_rings(bm, keep_caps = True)
             if not edges:
-                self.report({'WARNING'},
-                            "No suitable Face selection found. Operation cancelled")
+                self.report({'WARNING'}, "No suitable selection found")
                 return False
 
             result = bmesh.ops.subdivide_edges(
-                            bm,
-                            edges=edges,
-                            cuts=int(self.num_cuts),
-                            use_grid_fill=bool(self.use_grid_fill),
-                            use_single_edge=bool(self.use_single_edge),
-                            quad_corner_type=eval("self." + self.corner_type)
-                            )
+                bm,
+                edges = edges,
+                cuts = int(self.num_cuts),
+                use_grid_fill = bool(self.use_grid_fill),
+                use_single_edge = bool(self.use_single_edge),
+                quad_corner_type = str(self.corner_type))
+
             bpy.ops.mesh.select_all(action='DESELECT')
             bm.select_mode = {'EDGE'}
 
@@ -248,19 +213,29 @@ class MESH_xOT_cut_faces(Operator):
         context.tool_settings.mesh_select_mode[:] = False, True, False
         # Try to select all possible loops
         bpy.ops.mesh.loop_multi_select(ring=False)
-
         return {'FINISHED'}
 
+def menu_deselect_boundary(self, context):
+    self.layout.operator(MESH_xOT_deselect_boundary.bl_idname)
+
+def menu_cut_faces(self, context):
+    self.layout.operator(MESH_xOT_cut_faces.bl_idname)
 
 def register():
     bpy.utils.register_class(MESH_xOT_deselect_boundary)
     bpy.utils.register_class(MESH_xOT_cut_faces)
 
+    if __name__ != "__main__":
+        bpy.types.VIEW3D_MT_select_edit_mesh.append(menu_deselect_boundary)
+        bpy.types.VIEW3D_MT_edit_mesh_faces.append(menu_cut_faces)
 
 def unregister():
     bpy.utils.unregister_class(MESH_xOT_deselect_boundary)
     bpy.utils.unregister_class(MESH_xOT_cut_faces)
 
+    if __name__ != "__main__":
+        bpy.types.VIEW3D_MT_select_edit_mesh.remove(menu_deselect_boundary)
+        bpy.types.VIEW3D_MT_edit_mesh_faces.remove(menu_cut_faces)
 
 if __name__ == "__main__":
     register()