initial comit of insert_edge_ring
authorFlorian Meyer <florianfelix@web.de>
Thu, 30 Aug 2012 20:56:16 +0000 (20:56 +0000)
committerFlorian Meyer <florianfelix@web.de>
Thu, 30 Aug 2012 20:56:16 +0000 (20:56 +0000)
mesh_insert_edge_ring.py [new file with mode: 0644]

diff --git a/mesh_insert_edge_ring.py b/mesh_insert_edge_ring.py
new file mode 100644 (file)
index 0000000..02467a4
--- /dev/null
@@ -0,0 +1,374 @@
+#Simplified BSD License
+#
+#Copyright (c) 2012, Florian Meyer
+#tstscr@web.de
+#All rights reserved.
+#
+#Redistribution and use in source and binary forms, with or without
+#modification, are permitted provided that the following conditions are met: 
+#
+#1. Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer. 
+#2. Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution. 
+#
+#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+#ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+#(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+#SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#################################################################
+bl_info = {
+    "name": "Insert Edge Ring",
+    "author": "tstscr (tstscr@web.de)",
+    "version": (1, 0),
+    "blender": (2, 6, 4),
+    "location": "View3D > Edge Specials > Insert edge ring (Ctrl Alt R)",
+    "description": "Insert an edge ring along the selected edge loop",
+    "warning": "",
+    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Mesh/Insert_Edge_Ring",
+    "tracker_url": "",
+    "category": "Mesh"}
+###########################################################################
+import bpy, bmesh, math
+from bpy.types import Operator
+from bpy.props import FloatProperty, BoolProperty, EnumProperty
+from mathutils import Vector
+from collections import deque
+from bmesh.utils import vert_separate
+###########################################################################
+def update(bme):
+    bme.verts.index_update()
+    bme.edges.index_update()
+
+def selected_edges(component, invert=False):
+    def is_vert(vert):
+        if invert:
+            return [e for e in component.link_edges if not e.select]
+        return [e for e in component.link_edges if e.select]
+    if type(component) == bmesh.types.BMVert:
+        return is_vert(component)
+    if type(component) == bmesh.types.BMEdge:
+        edges = []
+        for vert in component.verts:
+            edges.extend(is_vert(vert))
+        if component in edges:
+            edges.remove(component)
+        return edges
+
+def edge_loop_from(v_start):
+    
+    def walk(vert, vert_loop=deque()):
+        #print('from', vert.index)
+        edges_select = selected_edges(vert)
+        #print('length edges_select', len(edges_select))
+        if not vert_loop:
+            #print('inserting %d into vert_loop' %vert.index)
+            vert_loop.append(vert)
+        
+        for edge in edges_select:
+            other_vert = edge.other_vert(vert)
+            #print('other_vert %d' %other_vert.index)
+            
+            edge_is_valid = True
+            if edge.is_boundary \
+            or other_vert in vert_loop \
+            or len(edges_select) > 2 \
+            or len(selected_edges(other_vert)) > 2:
+                #print('is not valid')
+                edge_is_valid = False
+            
+            if edge_is_valid:
+                if vert == vert_loop[-1]:
+                    #print('appending %d' %other_vert.index)
+                    vert_loop.append(other_vert)
+                else:
+                    #print('prepending %d' %other_vert.index)
+                    vert_loop.appendleft(other_vert)
+                    
+                walk(other_vert, vert_loop)
+        
+        return vert_loop
+    #####################################
+    v_loop = walk(v_start)
+    #print('returning', [v.index for v in v_loop])
+    return v_loop
+
+
+def collect_edge_loops(bme):
+    edge_loops = []
+    verts_to_consider = [v for v in bme.verts if v.select]
+    
+    while verts_to_consider:
+        
+        v_start = verts_to_consider[-1]
+        #print('\nverts_to_consider', [v.index for v in verts_to_consider])
+        edge_loop = edge_loop_from(v_start)
+        #update(bme)
+        #print('edge_loop', [v.index for v in edge_loop])
+        for v in edge_loop:
+            try:
+                verts_to_consider.remove(v)
+            except:
+                print('tried to remove vert %d from verts_to_consider. \
+                       Failed somehow' %v.index)
+        
+        if len(edge_loop) >= 3:
+            edge_loops.append(edge_loop)
+        else:
+            for v in edge_loop:
+                v.select = False
+    
+    if not verts_to_consider:
+        #print('no more verts_to_consider')
+        pass
+    
+    return edge_loops
+
+
+def insert_edge_ring(self, context):
+    def split_edge_loop(vert_loop):
+        other_loop = deque()
+        new_loop = deque()
+        for vert in vert_loop:
+            #print('OPERATING ON VERT', vert.index)
+            edges = selected_edges(vert)
+            v_new = bmesh.utils.vert_separate(vert, edges)
+            #print('RIPPING vert %d into' %vert.index, [v.index for v in v_new][:], \
+            #       'along edges', [e.index for e in edges])
+            if not closed:
+                if len(v_new) == 2:
+                    other_loop.append([v for v in v_new if v != vert][0])
+                else:
+                    other_loop.append(vert)
+            
+            if closed:
+                if not new_loop:
+                    #print('start_new_loop')
+                    new_loop.append(v_new[0])
+                    other_loop.append(v_new[1])
+                else:
+                    neighbours = [e.other_vert(v_new[0]) for e in v_new[0].link_edges]
+                    #print('neighbours', [n.index for n in neighbours])
+                    for n in neighbours:
+                        if n in new_loop and v_new[0] not in new_loop:
+                            #print('v_detect')
+                            new_loop.append(v_new[0])
+                            other_loop.append(v_new[1])
+                        if n in other_loop and v_new[0] not in other_loop:
+                            #print('v_not_detect')
+                            new_loop.append(v_new[1])
+                            other_loop.append(v_new[0])
+                
+        return other_loop, new_loop
+    
+    def move_verts(vert_loop, other_vert_loop):
+        
+        ### Offsets ###
+        def calc_offsets():
+            #print('\nCALCULATING OFFSETS')
+            offset = {}
+            for i, vert in enumerate(vert_loop):
+                edges_select = selected_edges(vert)
+                edges_unselect = selected_edges(vert, invert=True)
+                
+                vert_opposite = other_vert_loop[i]
+                edges_select_opposite = selected_edges(vert_opposite)
+                edges_unselect_opposite = selected_edges(vert_opposite, invert=True)
+                
+                ### MESH END VERT
+                if vert == other_vert_loop[0] or vert == other_vert_loop[-1]:
+                    #print('vert %d is start-end in middle of mesh, \
+                    #       does not need moving\n' %vert.index)
+                    continue
+                
+                ### BOUNDARY VERT
+                if len(edges_select) == 1:
+                    #print('verts %d  %d are on boundary' \
+                    #%(vert.index, other_vert_loop[i].index))
+                    border_edge = [e for e in edges_unselect if e.is_boundary][0]
+                    off = (border_edge.other_vert(vert).co - vert.co).normalized()
+                    if self.direction == 'LEFT':
+                        off *= 0
+                    offset[vert] = off
+                    #opposite vert
+                    border_edge_opposite = [e for e in edges_unselect_opposite \
+                                            if e.is_boundary][0]
+                    off = (border_edge_opposite.other_vert(vert_opposite).co \
+                           - vert_opposite.co).normalized()
+                    if self.direction == 'RIGHT':
+                        off *= 0
+                    offset[vert_opposite] = off
+                    continue
+                
+                ### MIDDLE VERT
+                if len(edges_select) == 2:
+                    #print('\nverts %d  %d are in middle of loop' \
+                    #%(vert.index, other_vert_loop[i].index))
+                    tangents = [e.calc_tangent(e.link_loops[0]) for e in edges_select]
+                    off = (tangents[0] + tangents[1]).normalized()
+                    angle = tangents[0].angle(tangents[1])
+                    if self.even:
+                        off += off * angle * 0.263910
+                    if self.direction == 'LEFT':
+                        off *= 0
+                    offset[vert] = off
+                    #opposite vert
+                    tangents = [e.calc_tangent(e.link_loops[0]) \
+                                for e in edges_select_opposite]
+                    off = (tangents[0] + tangents[1]).normalized()
+                    #angle= tangents[0].angle(tangents[1])
+                    if self.even:
+                        off += off * angle * 0.263910
+                    if self.direction == 'RIGHT':
+                        off *= 0
+                    offset[vert_opposite] = off
+                    continue
+            
+            return offset
+        
+        ### Moving ###
+        def move(offsets):
+            #print('\nMOVING VERTS')
+            for vert in offsets:
+                vert.co += offsets[vert] * self.distance
+        
+        offsets = calc_offsets()
+        move(offsets)
+    
+    def generate_new_geo(vert_loop, other_vert_loop):
+        #print('\nGENERATING NEW GEOMETRY')
+        
+        for i, vert in enumerate(vert_loop):
+            if vert == other_vert_loop[i]:
+                continue
+            edge_new = bme.edges.new([vert, other_vert_loop[i]])
+            edge_new.select = True
+        
+        bpy.ops.mesh.edge_face_add()
+
+    #####################################################################################
+    #####################################################################################
+    #####################################################################################
+    
+    bme = bmesh.from_edit_mesh(context.object.data)
+
+    ### COLLECT EDGE LOOPS ###
+    e_loops = collect_edge_loops(bme)
+    
+    for e_loop in e_loops:
+        
+        #check for closed loop - douple vert at start-end
+        closed = False
+        edges_select = selected_edges(e_loop[0])
+        for e in edges_select:
+            if e_loop[-1] in e.verts:
+                closed = True
+        
+        ### SPLITTING OF EDGES
+        other_vert_loop, new_loop = split_edge_loop(e_loop)
+        if closed:
+            e_loop = new_loop
+    
+        ### MOVE RIPPED VERTS ###
+        move_verts(e_loop, other_vert_loop)
+        
+        ### GENERATE NEW GEOMETRY ###
+        if self.generate_geo:
+            generate_new_geo(e_loop, other_vert_loop)
+    
+    update(bme)
+    
+###########################################################################
+# OPERATOR
+class MESH_OT_Insert_Edge_Ring(Operator):
+    """insert_edge_ring"""
+    bl_idname = "mesh.insert_edge_ring"
+    bl_label = "Insert edge ring"
+    bl_description = "Insert an edge ring along the selected edge loop"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    distance = FloatProperty(
+            name="distance",
+            default=0.01,
+            min=0, soft_min=0,
+            precision=4,
+            description="distance to move verts from original location")
+    
+    even = BoolProperty(
+            name='even',
+            default=True,
+            description='keep 90 degrees angles straight')
+    
+    generate_geo = BoolProperty(
+            name='Generate Geo',
+            default=True,
+            description='Fill edgering with faces')
+    
+    direction = EnumProperty(
+            name='direction',
+            description='Direction in which to expand the edge_ring',
+            items={
+            ('LEFT', '<|', 'only move verts left of loop (arbitrary)'),
+            ('CENTER', '<|>', 'move verts on both sides of loop'),
+            ('RIGHT', '|>', 'only move verts right of loop (arbitrary)'),
+            },
+            default='CENTER')
+    
+    def draw(self, context):
+        layout = self.layout
+        col = layout.column(align=True)
+        
+        col.prop(self, 'distance', slider=False)
+        col.prop(self, 'even', toggle=True)
+        col.prop(self, 'generate_geo', toggle=True)
+        col.separator()
+        col.label(text='Direction')
+        row = layout.row(align=True)
+        row.prop(self, 'direction', expand=True)
+        
+    @classmethod
+    def poll(cls, context):
+        return context.mode == 'EDIT_MESH'
+
+    def execute(self, context):
+        #print('\nInserting edge ring')
+        insert_edge_ring(self, context)
+        return {'FINISHED'}
+
+def insert_edge_ring_button(self, context):
+    self.layout.operator(MESH_OT_Insert_Edge_Ring.bl_idname,
+                         text="Insert edge ring")
+###########################################################################
+# REGISTRATION
+
+def register():
+    bpy.utils.register_module(__name__)
+    bpy.types.VIEW3D_MT_edit_mesh_edges.append(insert_edge_ring_button)
+
+    kc = bpy.context.window_manager.keyconfigs.addon
+    if kc:
+        km = kc.keymaps.new(name="3D View", space_type="VIEW_3D")
+        kmi = km.keymap_items.new('mesh.insert_edge_ring', \
+                                  'R', 'PRESS', ctrl=True, alt=True)
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+    bpy.types.VIEW3D_MT_edit_mesh_edges.remove(insert_edge_ring_button)
+
+    kc = bpy.context.window_manager.keyconfigs.addon
+    if kc:
+        km = kc.keymaps["3D View"]
+        for kmi in km.keymap_items:
+            if kmi.idname == 'mesh.insert_edge_ring':
+                km.keymap_items.remove(kmi)
+                break
+
+if __name__ == "__main__":
+    register()
\ No newline at end of file