diff --git a/mesh_f2.py b/mesh_f2.py new file mode 100644 index 0000000000000000000000000000000000000000..b06bb19f967db6804e4ba2515d11d5001ff0b4a6 --- /dev/null +++ b/mesh_f2.py @@ -0,0 +1,298 @@ +# ##### 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() \ No newline at end of file