load objects before linking materials (minor change)
[blender-addons.git] / mesh_f2.py
blobf602e0b5d7463ed12c55d9f84d83e27057e7dcec
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 #####
19 # <pep8 compliant>
21 bl_info = {
22 'name': "F2",
23 'author': "Bart Crouch",
24 'version': (1, 5, 0),
25 'blender': (2, 66, 3),
26 'location': "Editmode > F",
27 'warning': "",
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",
33 'category': 'Mesh'}
36 import bmesh
37 import bpy
38 import itertools
39 import mathutils
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] \
54 for i in range(2)]
55 if not all_edges[0] or not all_edges[1]:
56 return
58 # determine which edges to use, based on mouse cursor position
59 mouse_pos = mathutils.Vector([event.mouse_region_x, event.mouse_region_y])
60 optimal_edges = []
61 for edges in all_edges:
62 min_dist = False
63 for edge in 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,
67 region_3d, world_pos)
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]
81 # normal detection
82 flip_align = True
83 normal_edge = edge_1
84 if not normal_edge.link_faces:
85 normal_edge = edge_2
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
90 flip_align = False
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]
93 if v3 in ref_verts:
94 va_1 = v3
95 va_2 = v1
96 elif normal_edge == edge_sel:
97 va_1 = v1
98 va_2 = v2
99 else:
100 va_1 = v2
101 va_2 = v4
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
108 flip_align = False
110 # material index detection
111 ref_faces = edge_sel.link_faces
112 if not ref_faces:
113 ref_faces = edge_sel.verts[0].link_faces
114 if not ref_faces:
115 ref_faces = edge_sel.verts[1].link_faces
116 if not ref_faces:
117 mat_index = False
118 smooth = False
119 else:
120 mat_index = ref_faces[0].material_index
121 smooth = ref_faces[0].smooth
123 # create quad
124 try:
125 verts = [v3, v1, v2, v4]
126 if flip_align:
127 verts.reverse()
128 face = bm.faces.new(verts)
129 if mat_index:
130 face.material_index = mat_index
131 face.smooth = smooth
132 except:
133 # face already exists
134 return
136 # change selection
137 edge_sel.select = False
138 for vert in edge_sel.verts:
139 vert.select = False
140 for edge in face.edges:
141 if edge.index < 0:
142 edge.select = True
143 v3.select = True
144 v4.select = True
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]
159 if len(edges) < 2:
160 return
162 # determine which edges to use, based on mouse cursor position
163 min_dist = False
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 \
167 if not vert.select]
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,
173 world_pos)
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
179 edges = min_dist[1]
180 other_verts = min_dist[2]
181 new_pos = min_dist[3]
182 vert_new = bm.verts.new(new_pos)
184 # normal detection
185 flip_align = True
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
191 flip_align = False
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]
196 va_2 = vert_sel
197 else:
198 va_1 = vert_sel
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
206 flip_align = False
208 # material index detection
209 ref_faces = vert_sel.link_faces
210 if not ref_faces:
211 mat_index = False
212 smooth = False
213 else:
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]
219 if flip_align:
220 verts.reverse()
221 face = bm.faces.new(verts)
222 if mat_index:
223 face.material_index = mat_index
224 face.smooth = smooth
226 # change selection
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):
236 """Tooltip"""
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'}
242 @classmethod
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]
251 if len(sel) > 2:
252 # original 'Make Edge/Face' behaviour
253 try:
254 bpy.ops.mesh.edge_face_add('INVOKE_DEFAULT')
255 except:
256 return {'CANCELLED'}
257 elif len(sel) == 1:
258 # single vertex selected -> mirror vertex and create new face
259 quad_from_vertex(bm, sel[0], context, event)
260 elif len(sel) == 2:
261 edges_sel = [ed for ed in bm.edges if ed.select]
262 if len(edges_sel) != 1:
263 # 2 vertices selected, but not on the same edge
264 bpy.ops.mesh.edge_face_add()
265 else:
266 # single edge selected -> new face from linked open edges
267 quad_from_edge(bm, edges_sel[0], context, event)
269 return {'FINISHED'}
272 # registration
273 classes = [MeshF2]
274 addon_keymaps = []
277 def register():
278 # add operator
279 for c in classes:
280 bpy.utils.register_class(c)
282 # add keymap entry
283 km = bpy.context.window_manager.keyconfigs.addon.keymaps.new(\
284 name='Mesh', space_type='EMPTY')
285 kmi = km.keymap_items.new("mesh.f2", 'F', 'PRESS')
286 addon_keymaps.append(km)
289 def unregister():
290 # remove operator
291 for c in classes:
292 bpy.utils.unregister_class(c)
294 # remove keymap entry
295 for km in addon_keymaps:
296 bpy.context.window_manager.keyconfigs.addon.keymaps.remove(km)
297 addon_keymaps.clear()
300 if __name__ == "__main__":
301 register()