1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
23 'author': "Bart Crouch",
25 'blender': (2, 65, 9),
26 'location': "Editmode > F",
28 'description': "Extends the 'Make Edge/Face' functionality",
29 'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
30 "Scripts/Modeling/F2",
31 'tracker_url': "http://projects.blender.org/tracker/index.php?"\
32 "func=detail&aid=33979",
40 from bpy_extras
import view3d_utils
43 # create a face from a single selected edge
44 def quad_from_edge(bm
, edge_sel
, context
, event
):
45 ob
= context
.active_object
46 region
= context
.region
47 region_3d
= context
.space_data
.region_3d
49 # find linked edges that are open (<2 faces connected) and not part of
50 # the face the selected edge belongs to
51 all_edges
= [[edge
for edge
in edge_sel
.verts
[i
].link_edges
if \
52 len(edge
.link_faces
) < 2 and edge
!= edge_sel
and \
53 sum([face
in edge_sel
.link_faces
for face
in edge
.link_faces
]) == 0] \
55 if not all_edges
[0] or not all_edges
[1]:
58 # determine which edges to use, based on mouse cursor position
59 mouse_pos
= mathutils
.Vector([event
.mouse_region_x
, event
.mouse_region_y
])
61 for edges
in all_edges
:
64 vert
= [vert
for vert
in edge
.verts
if not vert
.select
][0]
65 world_pos
= ob
.matrix_world
* vert
.co
.copy()
66 screen_pos
= view3d_utils
.location_3d_to_region_2d(region
,
68 dist
= (mouse_pos
- screen_pos
).length
69 if not min_dist
or dist
< min_dist
[0]:
70 min_dist
= (dist
, edge
, vert
)
71 optimal_edges
.append(min_dist
)
73 # determine the vertices, which make up the quad
74 v1
= edge_sel
.verts
[0]
75 v2
= edge_sel
.verts
[1]
76 edge_1
= optimal_edges
[0][1]
77 edge_2
= optimal_edges
[1][1]
78 v3
= optimal_edges
[0][2]
79 v4
= optimal_edges
[1][2]
84 if not normal_edge
.link_faces
:
86 if not normal_edge
.link_faces
:
87 normal_edge
= edge_sel
88 if not normal_edge
.link_faces
:
89 # no connected faces, so no need to flip the face normal
91 if flip_align
: # there is a face to which the normal can be aligned
92 ref_verts
= [v
for v
in normal_edge
.link_faces
[0].verts
]
96 elif normal_edge
== edge_sel
:
102 if (va_1
== ref_verts
[0] and va_2
== ref_verts
[-1]) or \
103 (va_2
== ref_verts
[0] and va_1
== ref_verts
[-1]):
104 # reference verts are at start and end of the list -> shift list
105 ref_verts
= ref_verts
[1:] + [ref_verts
[0]]
106 if ref_verts
.index(va_1
) > ref_verts
.index(va_2
):
107 # connected face has same normal direction, so don't flip
110 # material index detection
111 ref_faces
= edge_sel
.link_faces
113 ref_faces
= edge_sel
.verts
[0].link_faces
115 ref_faces
= edge_sel
.verts
[1].link_faces
120 mat_index
= ref_faces
[0].material_index
121 smooth
= ref_faces
[0].smooth
125 verts
= [v3
, v1
, v2
, v4
]
128 face
= bm
.faces
.new(verts
)
130 face
.material_index
= mat_index
133 # face already exists
137 edge_sel
.select
= False
138 for vert
in edge_sel
.verts
:
140 for edge
in face
.edges
:
146 # toggle mode, to force correct drawing
147 bpy
.ops
.object.mode_set(mode
='OBJECT')
148 bpy
.ops
.object.mode_set(mode
='EDIT')
151 # create a face from a single selected vertex, if it is an open vertex
152 def quad_from_vertex(bm
, vert_sel
, context
, event
):
153 ob
= context
.active_object
154 region
= context
.region
155 region_3d
= context
.space_data
.region_3d
157 # find linked edges that are open (<2 faces connected)
158 edges
= [edge
for edge
in vert_sel
.link_edges
if len(edge
.link_faces
) < 2]
162 # determine which edges to use, based on mouse cursor position
164 mouse_pos
= mathutils
.Vector([event
.mouse_region_x
, event
.mouse_region_y
])
165 for a
, b
in itertools
.combinations(edges
, 2):
166 other_verts
= [vert
for edge
in [a
, b
] for vert
in edge
.verts \
168 mid_other
= (other_verts
[0].co
.copy() + other_verts
[1].co
.copy()) \
170 new_pos
= 2 * (mid_other
- vert_sel
.co
.copy()) + vert_sel
.co
.copy()
171 world_pos
= ob
.matrix_world
* new_pos
172 screen_pos
= view3d_utils
.location_3d_to_region_2d(region
, region_3d
,
174 dist
= (mouse_pos
- screen_pos
).length
175 if not min_dist
or dist
< min_dist
[0]:
176 min_dist
= (dist
, (a
, b
), other_verts
, new_pos
)
178 # create vertex at location mirrored in the line, connecting the open edges
180 other_verts
= min_dist
[2]
181 new_pos
= min_dist
[3]
182 vert_new
= bm
.verts
.new(new_pos
)
186 normal_edge
= edges
[0]
187 if not normal_edge
.link_faces
:
188 normal_edge
= edges
[1]
189 if not normal_edge
.link_faces
:
190 # no connected faces, so no need to flip the face normal
192 if flip_align
: # there is a face to which the normal can be aligned
193 ref_verts
= [v
for v
in normal_edge
.link_faces
[0].verts
]
194 if other_verts
[0] in ref_verts
:
195 va_1
= other_verts
[0]
199 va_2
= other_verts
[1]
200 if (va_1
== ref_verts
[0] and va_2
== ref_verts
[-1]) or \
201 (va_2
== ref_verts
[0] and va_1
== ref_verts
[-1]):
202 # reference verts are at start and end of the list -> shift list
203 ref_verts
= ref_verts
[1:] + [ref_verts
[0]]
204 if ref_verts
.index(va_1
) > ref_verts
.index(va_2
):
205 # connected face has same normal direction, so don't flip
208 # material index detection
209 ref_faces
= vert_sel
.link_faces
214 mat_index
= ref_faces
[0].material_index
215 smooth
= ref_faces
[0].smooth
217 # create face between all 4 vertices involved
218 verts
= [other_verts
[0], vert_sel
, other_verts
[1], vert_new
]
221 face
= bm
.faces
.new(verts
)
223 face
.material_index
= mat_index
227 vert_new
.select
= True
228 vert_sel
.select
= False
230 # toggle mode, to force correct drawing
231 bpy
.ops
.object.mode_set(mode
='OBJECT')
232 bpy
.ops
.object.mode_set(mode
='EDIT')
235 class MeshF2(bpy
.types
.Operator
):
237 bl_idname
= "mesh.f2"
238 bl_label
= "Make Edge/Face"
239 bl_description
= "Extends the 'Make Edge/Face' functionality"
240 bl_options
= {'REGISTER', 'UNDO'}
243 def poll(cls
, context
):
244 # check we are in mesh editmode
245 ob
= context
.active_object
246 return(ob
and ob
.type == 'MESH' and context
.mode
== 'EDIT_MESH')
248 def invoke(self
, context
, event
):
249 bm
= bmesh
.from_edit_mesh(context
.active_object
.data
)
250 sel
= [v
for v
in bm
.verts
if v
.select
]
252 # original 'Make Edge/Face' behaviour
253 bpy
.ops
.mesh
.edge_face_add()
255 # single vertex selected -> mirror vertex and create new face
256 quad_from_vertex(bm
, sel
[0], context
, event
)
258 edges_sel
= [ed
for ed
in bm
.edges
if ed
.select
]
259 if len(edges_sel
) != 1:
260 # 2 vertices selected, but not on the same edge
261 bpy
.ops
.mesh
.edge_face_add()
263 # single edge selected -> new face from linked open edges
264 quad_from_edge(bm
, edges_sel
[0], context
, event
)
277 bpy
.utils
.register_class(c
)
280 km
= bpy
.context
.window_manager
.keyconfigs
.addon
.keymaps
.new(\
281 name
='Mesh', space_type
='EMPTY')
282 kmi
= km
.keymap_items
.new("mesh.f2", 'F', 'PRESS')
283 addon_keymaps
.append(km
)
289 bpy
.utils
.unregister_class(c
)
291 # remove keymap entry
292 for km
in addon_keymaps
:
293 bpy
.context
.window_manager
.keyconfigs
.addon
.keymaps
.remove(km
)
294 addon_keymaps
.clear()
297 if __name__
== "__main__":