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, Alexander Nedovizin, Paul Kotelevets "
26 "blender": (2, 70, 0),
27 "location": "Editmode > F",
29 "description": "Extends the 'Make Edge/Face' functionality",
30 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
31 "Scripts/Modeling/F2",
40 from bpy_extras
import view3d_utils
43 # returns a custom data layer of the UV map, or None
44 def get_uv_layer(ob
, bm
, mat_index
):
47 if not ob
.material_slots
:
50 uv
= me
.uv_textures
.active
.name
52 mat
= ob
.material_slots
[mat_index
].material
53 slot
= mat
.texture_slots
[mat
.active_texture_index
]
54 if slot
and slot
.uv_layer
:
57 for tex_slot
in mat
.texture_slots
:
58 if tex_slot
and tex_slot
.uv_layer
:
59 uv
= tex_slot
.uv_layer
62 uv_layer
= bm
.loops
.layers
.uv
.get(uv
)
67 # create a face from a single selected edge
68 def quad_from_edge(bm
, edge_sel
, context
, event
):
69 ob
= context
.active_object
70 region
= context
.region
71 region_3d
= context
.space_data
.region_3d
73 # find linked edges that are open (<2 faces connected) and not part of
74 # the face the selected edge belongs to
75 all_edges
= [[edge
for edge
in edge_sel
.verts
[i
].link_edges
if \
76 len(edge
.link_faces
) < 2 and edge
!= edge_sel
and \
77 sum([face
in edge_sel
.link_faces
for face
in edge
.link_faces
]) == 0] \
79 if not all_edges
[0] or not all_edges
[1]:
82 # determine which edges to use, based on mouse cursor position
83 mouse_pos
= mathutils
.Vector([event
.mouse_region_x
, event
.mouse_region_y
])
85 for edges
in all_edges
:
88 vert
= [vert
for vert
in edge
.verts
if not vert
.select
][0]
89 world_pos
= ob
.matrix_world
* vert
.co
.copy()
90 screen_pos
= view3d_utils
.location_3d_to_region_2d(region
,
92 dist
= (mouse_pos
- screen_pos
).length
93 if not min_dist
or dist
< min_dist
[0]:
94 min_dist
= (dist
, edge
, vert
)
95 optimal_edges
.append(min_dist
)
97 # determine the vertices, which make up the quad
98 v1
= edge_sel
.verts
[0]
99 v2
= edge_sel
.verts
[1]
100 edge_1
= optimal_edges
[0][1]
101 edge_2
= optimal_edges
[1][1]
102 v3
= optimal_edges
[0][2]
103 v4
= optimal_edges
[1][2]
108 if not normal_edge
.link_faces
:
110 if not normal_edge
.link_faces
:
111 normal_edge
= edge_sel
112 if not normal_edge
.link_faces
:
113 # no connected faces, so no need to flip the face normal
115 if flip_align
: # there is a face to which the normal can be aligned
116 ref_verts
= [v
for v
in normal_edge
.link_faces
[0].verts
]
120 elif normal_edge
== edge_sel
:
126 if (va_1
== ref_verts
[0] and va_2
== ref_verts
[-1]) or \
127 (va_2
== ref_verts
[0] and va_1
== ref_verts
[-1]):
128 # reference verts are at start and end of the list -> shift list
129 ref_verts
= ref_verts
[1:] + [ref_verts
[0]]
130 if ref_verts
.index(va_1
) > ref_verts
.index(va_2
):
131 # connected face has same normal direction, so don't flip
134 # material index detection
135 ref_faces
= edge_sel
.link_faces
137 ref_faces
= edge_sel
.verts
[0].link_faces
139 ref_faces
= edge_sel
.verts
[1].link_faces
144 mat_index
= ref_faces
[0].material_index
145 smooth
= ref_faces
[0].smooth
150 # triangle (usually at end of quad-strip
153 # normal face creation
154 verts
= [v3
, v1
, v2
, v4
]
157 face
= bm
.faces
.new(verts
)
159 face
.material_index
= mat_index
162 # face already exists
166 edge_sel
.select
= False
167 for vert
in edge_sel
.verts
:
169 for edge
in face
.edges
:
176 if __name__
!= '__main__':
177 addon_prefs
= context
.user_preferences
.addons
[__name__
].preferences
178 if addon_prefs
.adjustuv
:
179 uv_layer
= get_uv_layer(ob
, bm
, mat_index
)
182 for vert
in [v1
, v2
, v3
, v4
]:
183 for loop
in vert
.link_loops
:
184 if loop
.face
.index
> -1:
185 uv_ori
[loop
.vert
.index
] = loop
[uv_layer
].uv
186 if len(uv_ori
) == 4 or len(uv_ori
) == 3:
187 for loop
in face
.loops
:
188 loop
[uv_layer
].uv
= uv_ori
[loop
.vert
.index
]
190 # toggle mode, to force correct drawing
191 bpy
.ops
.object.mode_set(mode
='OBJECT')
192 bpy
.ops
.object.mode_set(mode
='EDIT')
195 # create a face from a single selected vertex, if it is an open vertex
196 def quad_from_vertex(bm
, vert_sel
, context
, event
):
197 ob
= context
.active_object
199 region
= context
.region
200 region_3d
= context
.space_data
.region_3d
202 # find linked edges that are open (<2 faces connected)
203 edges
= [edge
for edge
in vert_sel
.link_edges
if len(edge
.link_faces
) < 2]
207 # determine which edges to use, based on mouse cursor position
209 mouse_pos
= mathutils
.Vector([event
.mouse_region_x
, event
.mouse_region_y
])
210 for a
, b
in itertools
.combinations(edges
, 2):
211 other_verts
= [vert
for edge
in [a
, b
] for vert
in edge
.verts \
213 mid_other
= (other_verts
[0].co
.copy() + other_verts
[1].co
.copy()) \
215 new_pos
= 2 * (mid_other
- vert_sel
.co
.copy()) + vert_sel
.co
.copy()
216 world_pos
= ob
.matrix_world
* new_pos
217 screen_pos
= view3d_utils
.location_3d_to_region_2d(region
, region_3d
,
219 dist
= (mouse_pos
- screen_pos
).length
220 if not min_dist
or dist
< min_dist
[0]:
221 min_dist
= (dist
, (a
, b
), other_verts
, new_pos
)
223 # create vertex at location mirrored in the line, connecting the open edges
225 other_verts
= min_dist
[2]
226 new_pos
= min_dist
[3]
227 vert_new
= bm
.verts
.new(new_pos
)
231 normal_edge
= edges
[0]
232 if not normal_edge
.link_faces
:
233 normal_edge
= edges
[1]
234 if not normal_edge
.link_faces
:
235 # no connected faces, so no need to flip the face normal
237 if flip_align
: # there is a face to which the normal can be aligned
238 ref_verts
= [v
for v
in normal_edge
.link_faces
[0].verts
]
239 if other_verts
[0] in ref_verts
:
240 va_1
= other_verts
[0]
244 va_2
= other_verts
[1]
245 if (va_1
== ref_verts
[0] and va_2
== ref_verts
[-1]) or \
246 (va_2
== ref_verts
[0] and va_1
== ref_verts
[-1]):
247 # reference verts are at start and end of the list -> shift list
248 ref_verts
= ref_verts
[1:] + [ref_verts
[0]]
249 if ref_verts
.index(va_1
) > ref_verts
.index(va_2
):
250 # connected face has same normal direction, so don't flip
253 # material index detection
254 ref_faces
= vert_sel
.link_faces
259 mat_index
= ref_faces
[0].material_index
260 smooth
= ref_faces
[0].smooth
262 # create face between all 4 vertices involved
263 verts
= [other_verts
[0], vert_sel
, other_verts
[1], vert_new
]
266 face
= bm
.faces
.new(verts
)
268 face
.material_index
= mat_index
272 vert_new
.select
= True
273 vert_sel
.select
= False
276 if __name__
!= '__main__':
277 addon_prefs
= context
.user_preferences
.addons
[__name__
].preferences
278 if addon_prefs
.adjustuv
:
279 uv_layer
= get_uv_layer(ob
, bm
, mat_index
)
284 # get original uv coordinates
286 for loop
in other_verts
[i
].link_loops
:
287 if loop
.face
.index
> -1:
288 uv_others
[loop
.vert
.index
] = loop
[uv_layer
].uv
290 if len(uv_others
) == 2:
291 mid_other
= (list(uv_others
.values())[0] +
292 list(uv_others
.values())[1]) / 2
293 for loop
in vert_sel
.link_loops
:
294 if loop
.face
.index
> -1:
295 uv_sel
= loop
[uv_layer
].uv
298 uv_new
= 2 * (mid_other
- uv_sel
) + uv_sel
300 # set uv coordinates for new loops
302 for loop
in face
.loops
:
303 if loop
.vert
.index
== -1:
305 elif loop
.vert
.index
in uv_others
:
306 x
, y
= uv_others
[loop
.vert
.index
]
309 loop
[uv_layer
].uv
= (x
, y
)
311 # toggle mode, to force correct drawing
312 bpy
.ops
.object.mode_set(mode
='OBJECT')
313 bpy
.ops
.object.mode_set(mode
='EDIT')
316 # autograb preference in addons panel
317 class F2AddonPreferences(bpy
.types
.AddonPreferences
):
319 adjustuv
= bpy
.props
.BoolProperty(
321 description
= "Automatically update UV unwrapping",
323 autograb
= bpy
.props
.BoolProperty(
325 description
= "Automatically puts a newly created vertex in grab mode",
328 def draw(self
, context
):
330 layout
.prop(self
, "autograb")
331 layout
.prop(self
, "adjustuv")
334 class MeshF2(bpy
.types
.Operator
):
336 bl_idname
= "mesh.f2"
337 bl_label
= "Make Edge/Face"
338 bl_description
= "Extends the 'Make Edge/Face' functionality"
339 bl_options
= {'REGISTER', 'UNDO'}
342 def poll(cls
, context
):
343 # check we are in mesh editmode
344 ob
= context
.active_object
345 return(ob
and ob
.type == 'MESH' and context
.mode
== 'EDIT_MESH')
347 def invoke(self
, context
, event
):
348 bm
= bmesh
.from_edit_mesh(context
.active_object
.data
)
349 sel
= [v
for v
in bm
.verts
if v
.select
]
351 # original 'Make Edge/Face' behaviour
353 bpy
.ops
.mesh
.edge_face_add('INVOKE_DEFAULT')
357 # single vertex selected -> mirror vertex and create new face
358 quad_from_vertex(bm
, sel
[0], context
, event
)
359 if __name__
!= '__main__':
360 addon_prefs
= context
.user_preferences
.addons
[__name__
].\
362 if addon_prefs
.autograb
:
363 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
365 edges_sel
= [ed
for ed
in bm
.edges
if ed
.select
]
366 if len(edges_sel
) != 1:
367 # 2 vertices selected, but not on the same edge
368 bpy
.ops
.mesh
.edge_face_add()
370 # single edge selected -> new face from linked open edges
371 quad_from_edge(bm
, edges_sel
[0], context
, event
)
377 classes
= [MeshF2
, F2AddonPreferences
]
384 bpy
.utils
.register_class(c
)
387 km
= bpy
.context
.window_manager
.keyconfigs
.addon
.keymaps
.new(\
388 name
='Mesh', space_type
='EMPTY')
389 kmi
= km
.keymap_items
.new("mesh.f2", 'F', 'PRESS')
390 addon_keymaps
.append((km
, kmi
))
394 # remove keymap entry
395 for km
, kmi
in addon_keymaps
:
396 km
.keymap_items
.remove(kmi
)
397 addon_keymaps
.clear()
399 # remove operator and preferences
400 for c
in reversed(classes
):
401 bpy
.utils
.unregister_class(c
)
404 if __name__
== "__main__":