# 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, 64, 0), "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": "https://developer.blender.org/T32424", "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()