Skip to content
Snippets Groups Projects
mesh_f2.py 9.86 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    
    bl_info = {
        'name': "F2",
        'author': "Bart Crouch",
        'version': (1, 4, 0),
        'blender': (2, 65, 9),
        'location': "Editmode > F",
        'warning': "",
        'description': "Extends the 'Make Edge/Face' functionality",
        'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
            "Scripts/Modeling/F2",
        'tracker_url': "http://projects.blender.org/tracker/index.php?"\
            "func=detail&aid=33979",
        'category': 'Mesh'}
    
    
    import bmesh
    import bpy
    import itertools
    import mathutils
    from bpy_extras import view3d_utils
    
    
    # create a face from a single selected edge
    def quad_from_edge(bm, edge_sel, context, event):
        ob = context.active_object
        region = context.region
        region_3d = context.space_data.region_3d
    
        # find linked edges that are open (<2 faces connected) and not part of
        # the face the selected edge belongs to
        all_edges = [[edge for edge in edge_sel.verts[i].link_edges if \
            len(edge.link_faces) < 2 and edge != edge_sel and \
            sum([face in edge_sel.link_faces for face in edge.link_faces]) == 0] \
            for i in range(2)]
        if not all_edges[0] or not all_edges[1]:
            return
    
        # determine which edges to use, based on mouse cursor position
        mouse_pos = mathutils.Vector([event.mouse_region_x, event.mouse_region_y])
        optimal_edges = []
        for edges in all_edges:
            min_dist = False
            for edge in edges:
                vert = [vert for vert in edge.verts if not vert.select][0]
                world_pos = ob.matrix_world * vert.co.copy()
                screen_pos = view3d_utils.location_3d_to_region_2d(region,
                    region_3d, world_pos)
                dist = (mouse_pos - screen_pos).length
                if not min_dist or dist < min_dist[0]:
                    min_dist = (dist, edge, vert)
            optimal_edges.append(min_dist)
    
        # determine the vertices, which make up the quad
        v1 = edge_sel.verts[0]
        v2 = edge_sel.verts[1]
        edge_1 = optimal_edges[0][1]
        edge_2 = optimal_edges[1][1]
        v3 = optimal_edges[0][2]
        v4 = optimal_edges[1][2]
    
        # normal detection
        flip_align = True
        normal_edge = edge_1
        if not normal_edge.link_faces:
            normal_edge = edge_2
            if not normal_edge.link_faces:
                normal_edge = edge_sel
                if not normal_edge.link_faces:
                    # no connected faces, so no need to flip the face normal
                    flip_align = False
        if flip_align: # there is a face to which the normal can be aligned
            ref_verts = [v for v in normal_edge.link_faces[0].verts]
            if v3 in ref_verts:
                va_1 = v3
                va_2 = v1
            elif normal_edge == edge_sel:
                va_1 = v1
                va_2 = v2
            else:
                va_1 = v2
                va_2 = v4
            if (va_1 == ref_verts[0] and va_2 == ref_verts[-1]) or \
            (va_2 == ref_verts[0] and va_1 == ref_verts[-1]):
                # reference verts are at start and end of the list -> shift list
                ref_verts = ref_verts[1:] + [ref_verts[0]]
            if ref_verts.index(va_1) > ref_verts.index(va_2):
                # connected face has same normal direction, so don't flip
                flip_align = False
    
        # material index detection
        ref_faces = edge_sel.link_faces
        if not ref_faces:
            ref_faces = edge_sel.verts[0].link_faces
        if not ref_faces:
            ref_faces = edge_sel.verts[1].link_faces
        if not ref_faces:
            mat_index = False
            smooth = False
        else:
            mat_index = ref_faces[0].material_index
            smooth = ref_faces[0].smooth
    
        # create quad
        try:
            verts = [v3, v1, v2, v4]
            if flip_align:
                verts.reverse()
            face = bm.faces.new(verts)
            if mat_index:
                face.material_index = mat_index
            face.smooth = smooth
        except:
            # face already exists
            return
    
        # change selection
        edge_sel.select = False
        for vert in edge_sel.verts:
            vert.select = False
        for edge in face.edges:
            if edge.index < 0:
                edge.select = True
        v3.select = True
        v4.select = True
    
        # toggle mode, to force correct drawing
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT')
    
    
    # create a face from a single selected vertex, if it is an open vertex
    def quad_from_vertex(bm, vert_sel, context, event):
        ob = context.active_object
        region = context.region
        region_3d = context.space_data.region_3d
    
        # find linked edges that are open (<2 faces connected)
        edges = [edge for edge in vert_sel.link_edges if len(edge.link_faces) < 2]
        if len(edges) < 2:
            return
    
        # determine which edges to use, based on mouse cursor position
        min_dist = False
        mouse_pos = mathutils.Vector([event.mouse_region_x, event.mouse_region_y])
        for a, b in itertools.combinations(edges, 2):
            other_verts = [vert for edge in [a, b] for vert in edge.verts \
                if not vert.select]
            mid_other = (other_verts[0].co.copy() + other_verts[1].co.copy()) \
                / 2
            new_pos = 2 * (mid_other - vert_sel.co.copy()) + vert_sel.co.copy()
            world_pos = ob.matrix_world * new_pos
            screen_pos = view3d_utils.location_3d_to_region_2d(region, region_3d,
                world_pos)
            dist = (mouse_pos - screen_pos).length
            if not min_dist or dist < min_dist[0]:
                min_dist = (dist, (a, b), other_verts, new_pos)
    
        # create vertex at location mirrored in the line, connecting the open edges
        edges = min_dist[1]
        other_verts = min_dist[2]
        new_pos = min_dist[3]
        vert_new = bm.verts.new(new_pos)
    
        # normal detection
        flip_align = True
        normal_edge = edges[0]
        if not normal_edge.link_faces:
            normal_edge = edges[1]
            if not normal_edge.link_faces:
                # no connected faces, so no need to flip the face normal
                    flip_align = False
        if flip_align: # there is a face to which the normal can be aligned
            ref_verts = [v for v in normal_edge.link_faces[0].verts]
            if other_verts[0] in ref_verts:
                va_1 = other_verts[0]
                va_2 = vert_sel
            else:
                va_1 = vert_sel
                va_2 = other_verts[1]
            if (va_1 == ref_verts[0] and va_2 == ref_verts[-1]) or \
            (va_2 == ref_verts[0] and va_1 == ref_verts[-1]):
                # reference verts are at start and end of the list -> shift list
                ref_verts = ref_verts[1:] + [ref_verts[0]]
            if ref_verts.index(va_1) > ref_verts.index(va_2):
                # connected face has same normal direction, so don't flip
                flip_align = False
    
        # material index detection
        ref_faces = vert_sel.link_faces
        if not ref_faces:
            mat_index = False
            smooth = False
        else:
            mat_index = ref_faces[0].material_index
            smooth = ref_faces[0].smooth
    
        # create face between all 4 vertices involved
        verts = [other_verts[0], vert_sel, other_verts[1], vert_new]
        if flip_align:
            verts.reverse()
        face = bm.faces.new(verts)
        if mat_index:
            face.material_index = mat_index
        face.smooth = smooth
    
        # change selection
        vert_new.select = True
        vert_sel.select = False
    
        # toggle mode, to force correct drawing
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT')
    
    
    class MeshF2(bpy.types.Operator):
        """Tooltip"""
        bl_idname = "mesh.f2"
        bl_label = "Make Edge/Face"
        bl_description = "Extends the 'Make Edge/Face' functionality"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            # check we are in mesh editmode
            ob = context.active_object
            return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
    
        def invoke(self, context, event):
            bm = bmesh.from_edit_mesh(context.active_object.data)
            sel = [v for v in bm.verts if v.select]
            if len(sel) > 2:
                # original 'Make Edge/Face' behaviour
                bpy.ops.mesh.edge_face_add()
            elif len(sel) == 1:
                # single vertex selected -> mirror vertex and create new face
                quad_from_vertex(bm, sel[0], context, event)
            elif len(sel) == 2:
                edges_sel = [ed for ed in bm.edges if ed.select]
                if len(edges_sel) != 1:
                    # 2 vertices selected, but not on the same edge
                    bpy.ops.mesh.edge_face_add()
                else:
                    # single edge selected -> new face from linked open edges
                    quad_from_edge(bm, edges_sel[0], context, event)
    
            return {'FINISHED'}
    
    
    # registration
    classes = [MeshF2]
    addon_keymaps = []
    
    
    def register():
        # add operator
        for c in classes:
            bpy.utils.register_class(c)
    
        # add keymap entry
        km = bpy.context.window_manager.keyconfigs.addon.keymaps.new(\
            name='Mesh', space_type='EMPTY')
        kmi = km.keymap_items.new("mesh.f2", 'F', 'PRESS')
        addon_keymaps.append(km)
    
    
    def unregister():
        # remove operator
        for c in classes:
            bpy.utils.unregister_class(c)
    
        # remove keymap entry
        for km in addon_keymaps:
            bpy.context.window_manager.keyconfigs.addon.keymaps.remove(km)
        addon_keymaps.clear()
    
    
    if __name__ == "__main__":
        register()