use new preferences standard and fixes for various blender changes
[blender-addons-contrib.git] / mesh_insert_edge_ring.py
1 #Simplified BSD License
2 #
3 #Copyright (c) 2012, Florian Meyer
4 #tstscr@web.de
5 #All rights reserved.
6 #
7 #Redistribution and use in source and binary forms, with or without
8 #modification, are permitted provided that the following conditions are met: 
9 #
10 #1. Redistributions of source code must retain the above copyright notice, this
11 #   list of conditions and the following disclaimer. 
12 #2. Redistributions in binary form must reproduce the above copyright notice,
13 #   this list of conditions and the following disclaimer in the documentation
14 #   and/or other materials provided with the distribution. 
15 #
16 #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 #ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 #WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 #DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 #ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 #(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 #LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 #ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 #(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 #SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #################################################################
27 bl_info = {
28     "name": "Insert Edge Ring",
29     "author": "tstscr (tstscr@web.de)",
30     "version": (1, 0),
31     "blender": (2, 64, 0),
32     "location": "View3D > Edge Specials > Insert edge ring (Ctrl Alt R)",
33     "description": "Insert an edge ring along the selected edge loop",
34     "warning": "",
35     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Mesh/Insert_Edge_Ring",
36     "tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=32424",
37     "category": "Mesh"}
38 ###########################################################################
39 import bpy, bmesh, math
40 from bpy.types import Operator
41 from bpy.props import FloatProperty, BoolProperty, EnumProperty
42 from mathutils import Vector
43 from collections import deque
44 from bmesh.utils import vert_separate
45 ###########################################################################
46 def update(bme):
47     bme.verts.index_update()
48     bme.edges.index_update()
49
50 def selected_edges(component, invert=False):
51     def is_vert(vert):
52         if invert:
53             return [e for e in component.link_edges if not e.select]
54         return [e for e in component.link_edges if e.select]
55     if type(component) == bmesh.types.BMVert:
56         return is_vert(component)
57     if type(component) == bmesh.types.BMEdge:
58         edges = []
59         for vert in component.verts:
60             edges.extend(is_vert(vert))
61         if component in edges:
62             edges.remove(component)
63         return edges
64
65 def edge_loop_from(v_start):
66     
67     def walk(vert, vert_loop=deque()):
68         #print('from', vert.index)
69         edges_select = selected_edges(vert)
70         #print('length edges_select', len(edges_select))
71         if not vert_loop:
72             #print('inserting %d into vert_loop' %vert.index)
73             vert_loop.append(vert)
74         
75         for edge in edges_select:
76             other_vert = edge.other_vert(vert)
77             #print('other_vert %d' %other_vert.index)
78             
79             edge_is_valid = True
80             if edge.is_boundary \
81             or other_vert in vert_loop \
82             or len(edges_select) > 2 \
83             or len(selected_edges(other_vert)) > 2:
84                 #print('is not valid')
85                 edge_is_valid = False
86             
87             if edge_is_valid:
88                 if vert == vert_loop[-1]:
89                     #print('appending %d' %other_vert.index)
90                     vert_loop.append(other_vert)
91                 else:
92                     #print('prepending %d' %other_vert.index)
93                     vert_loop.appendleft(other_vert)
94                     
95                 walk(other_vert, vert_loop)
96         
97         return vert_loop
98     #####################################
99     v_loop = walk(v_start)
100     #print('returning', [v.index for v in v_loop])
101     return v_loop
102
103
104 def collect_edge_loops(bme):
105     edge_loops = []
106     verts_to_consider = [v for v in bme.verts if v.select]
107     
108     while verts_to_consider:
109         
110         v_start = verts_to_consider[-1]
111         #print('\nverts_to_consider', [v.index for v in verts_to_consider])
112         edge_loop = edge_loop_from(v_start)
113         #update(bme)
114         #print('edge_loop', [v.index for v in edge_loop])
115         for v in edge_loop:
116             try:
117                 verts_to_consider.remove(v)
118             except:
119                 print('tried to remove vert %d from verts_to_consider. \
120                        Failed somehow' %v.index)
121         
122         if len(edge_loop) >= 3:
123             edge_loops.append(edge_loop)
124         else:
125             for v in edge_loop:
126                 v.select = False
127     
128     if not verts_to_consider:
129         #print('no more verts_to_consider')
130         pass
131     
132     return edge_loops
133
134
135 def insert_edge_ring(self, context):
136     def split_edge_loop(vert_loop):
137         other_loop = deque()
138         new_loop = deque()
139         for vert in vert_loop:
140             #print('OPERATING ON VERT', vert.index)
141             edges = selected_edges(vert)
142             v_new = bmesh.utils.vert_separate(vert, edges)
143             #print('RIPPING vert %d into' %vert.index, [v.index for v in v_new][:], \
144             #       'along edges', [e.index for e in edges])
145             if not closed:
146                 if len(v_new) == 2:
147                     other_loop.append([v for v in v_new if v != vert][0])
148                 else:
149                     other_loop.append(vert)
150             
151             if closed:
152                 if not new_loop:
153                     #print('start_new_loop')
154                     new_loop.append(v_new[0])
155                     other_loop.append(v_new[1])
156                 else:
157                     neighbours = [e.other_vert(v_new[0]) for e in v_new[0].link_edges]
158                     #print('neighbours', [n.index for n in neighbours])
159                     for n in neighbours:
160                         if n in new_loop and v_new[0] not in new_loop:
161                             #print('v_detect')
162                             new_loop.append(v_new[0])
163                             other_loop.append(v_new[1])
164                         if n in other_loop and v_new[0] not in other_loop:
165                             #print('v_not_detect')
166                             new_loop.append(v_new[1])
167                             other_loop.append(v_new[0])
168                 
169         return other_loop, new_loop
170     
171     def move_verts(vert_loop, other_vert_loop):
172         
173         ### Offsets ###
174         def calc_offsets():
175             #print('\nCALCULATING OFFSETS')
176             offset = {}
177             for i, vert in enumerate(vert_loop):
178                 edges_select = selected_edges(vert)
179                 edges_unselect = selected_edges(vert, invert=True)
180                 
181                 vert_opposite = other_vert_loop[i]
182                 edges_select_opposite = selected_edges(vert_opposite)
183                 edges_unselect_opposite = selected_edges(vert_opposite, invert=True)
184                 
185                 ### MESH END VERT
186                 if vert == other_vert_loop[0] or vert == other_vert_loop[-1]:
187                     #print('vert %d is start-end in middle of mesh, \
188                     #       does not need moving\n' %vert.index)
189                     continue
190                 
191                 ### BOUNDARY VERT
192                 if len(edges_select) == 1:
193                     #print('verts %d  %d are on boundary' \
194                     #%(vert.index, other_vert_loop[i].index))
195                     border_edge = [e for e in edges_unselect if e.is_boundary][0]
196                     off = (border_edge.other_vert(vert).co - vert.co).normalized()
197                     if self.direction == 'LEFT':
198                         off *= 0
199                     offset[vert] = off
200                     #opposite vert
201                     border_edge_opposite = [e for e in edges_unselect_opposite \
202                                             if e.is_boundary][0]
203                     off = (border_edge_opposite.other_vert(vert_opposite).co \
204                            - vert_opposite.co).normalized()
205                     if self.direction == 'RIGHT':
206                         off *= 0
207                     offset[vert_opposite] = off
208                     continue
209                 
210                 ### MIDDLE VERT
211                 if len(edges_select) == 2:
212                     #print('\nverts %d  %d are in middle of loop' \
213                     #%(vert.index, other_vert_loop[i].index))
214                     tangents = [e.calc_tangent(e.link_loops[0]) for e in edges_select]
215                     off = (tangents[0] + tangents[1]).normalized()
216                     angle = tangents[0].angle(tangents[1])
217                     if self.even:
218                         off += off * angle * 0.263910
219                     if self.direction == 'LEFT':
220                         off *= 0
221                     offset[vert] = off
222                     #opposite vert
223                     tangents = [e.calc_tangent(e.link_loops[0]) \
224                                 for e in edges_select_opposite]
225                     off = (tangents[0] + tangents[1]).normalized()
226                     #angle= tangents[0].angle(tangents[1])
227                     if self.even:
228                         off += off * angle * 0.263910
229                     if self.direction == 'RIGHT':
230                         off *= 0
231                     offset[vert_opposite] = off
232                     continue
233             
234             return offset
235         
236         ### Moving ###
237         def move(offsets):
238             #print('\nMOVING VERTS')
239             for vert in offsets:
240                 vert.co += offsets[vert] * self.distance
241         
242         offsets = calc_offsets()
243         move(offsets)
244     
245     def generate_new_geo(vert_loop, other_vert_loop):
246         #print('\nGENERATING NEW GEOMETRY')
247         
248         for i, vert in enumerate(vert_loop):
249             if vert == other_vert_loop[i]:
250                 continue
251             edge_new = bme.edges.new([vert, other_vert_loop[i]])
252             edge_new.select = True
253         
254         bpy.ops.mesh.edge_face_add()
255
256     #####################################################################################
257     #####################################################################################
258     #####################################################################################
259     
260     bme = bmesh.from_edit_mesh(context.object.data)
261
262     ### COLLECT EDGE LOOPS ###
263     e_loops = collect_edge_loops(bme)
264     
265     for e_loop in e_loops:
266         
267         #check for closed loop - douple vert at start-end
268         closed = False
269         edges_select = selected_edges(e_loop[0])
270         for e in edges_select:
271             if e_loop[-1] in e.verts:
272                 closed = True
273         
274         ### SPLITTING OF EDGES
275         other_vert_loop, new_loop = split_edge_loop(e_loop)
276         if closed:
277             e_loop = new_loop
278     
279         ### MOVE RIPPED VERTS ###
280         move_verts(e_loop, other_vert_loop)
281         
282         ### GENERATE NEW GEOMETRY ###
283         if self.generate_geo:
284             generate_new_geo(e_loop, other_vert_loop)
285     
286     update(bme)
287     
288 ###########################################################################
289 # OPERATOR
290 class MESH_OT_Insert_Edge_Ring(Operator):
291     """insert_edge_ring"""
292     bl_idname = "mesh.insert_edge_ring"
293     bl_label = "Insert edge ring"
294     bl_description = "Insert an edge ring along the selected edge loop"
295     bl_options = {'REGISTER', 'UNDO'}
296
297     distance = FloatProperty(
298             name="distance",
299             default=0.01,
300             min=0, soft_min=0,
301             precision=4,
302             description="distance to move verts from original location")
303     
304     even = BoolProperty(
305             name='even',
306             default=True,
307             description='keep 90 degrees angles straight')
308     
309     generate_geo = BoolProperty(
310             name='Generate Geo',
311             default=True,
312             description='Fill edgering with faces')
313     
314     direction = EnumProperty(
315             name='direction',
316             description='Direction in which to expand the edge_ring',
317             items={
318             ('LEFT', '<|', 'only move verts left of loop (arbitrary)'),
319             ('CENTER', '<|>', 'move verts on both sides of loop'),
320             ('RIGHT', '|>', 'only move verts right of loop (arbitrary)'),
321             },
322             default='CENTER')
323     
324     def draw(self, context):
325         layout = self.layout
326         col = layout.column(align=True)
327         
328         col.prop(self, 'distance', slider=False)
329         col.prop(self, 'even', toggle=True)
330         col.prop(self, 'generate_geo', toggle=True)
331         col.separator()
332         col.label(text='Direction')
333         row = layout.row(align=True)
334         row.prop(self, 'direction', expand=True)
335         
336     @classmethod
337     def poll(cls, context):
338         return context.mode == 'EDIT_MESH'
339
340     def execute(self, context):
341         #print('\nInserting edge ring')
342         insert_edge_ring(self, context)
343         return {'FINISHED'}
344
345 def insert_edge_ring_button(self, context):
346     self.layout.operator(MESH_OT_Insert_Edge_Ring.bl_idname,
347                          text="Insert edge ring")
348 ###########################################################################
349 # REGISTRATION
350
351 def register():
352     bpy.utils.register_module(__name__)
353     bpy.types.VIEW3D_MT_edit_mesh_edges.append(insert_edge_ring_button)
354
355     kc = bpy.context.window_manager.keyconfigs.addon
356     if kc:
357         km = kc.keymaps.new(name="3D View", space_type="VIEW_3D")
358         kmi = km.keymap_items.new('mesh.insert_edge_ring', \
359                                   'R', 'PRESS', ctrl=True, alt=True)
360
361 def unregister():
362     bpy.utils.unregister_module(__name__)
363     bpy.types.VIEW3D_MT_edit_mesh_edges.remove(insert_edge_ring_button)
364
365     kc = bpy.context.window_manager.keyconfigs.addon
366     if kc:
367         km = kc.keymaps["3D View"]
368         for kmi in km.keymap_items:
369             if kmi.idname == 'mesh.insert_edge_ring':
370                 km.keymap_items.remove(kmi)
371                 break
372
373 if __name__ == "__main__":
374     register()