Newer
Older
CoDEmanX
committed
# Simplified BSD License
CoDEmanX
committed
# Copyright (c) 2012, Florian Meyer
# tstscr@web.de
# All rights reserved.
CoDEmanX
committed
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
CoDEmanX
committed
# 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.
CoDEmanX
committed
# 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),
"location": "View3D > Edge Specials > Insert edge ring (Ctrl Alt R)",
"description": "Insert an edge ring along the selected edge loop",
"warning": "",
CoDEmanX
committed
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Mesh/Insert_Edge_Ring",
"tracker_url": "https://developer.blender.org/T32424",
CoDEmanX
committed
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
CoDEmanX
committed
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):
CoDEmanX
committed
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)
CoDEmanX
committed
for edge in edges_select:
other_vert = edge.other_vert(vert)
#print('other_vert %d' %other_vert.index)
CoDEmanX
committed
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
CoDEmanX
committed
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)
CoDEmanX
committed
CoDEmanX
committed
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]
CoDEmanX
committed
CoDEmanX
committed
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)
CoDEmanX
committed
if len(edge_loop) >= 3:
edge_loops.append(edge_loop)
else:
for v in edge_loop:
v.select = False
CoDEmanX
committed
if not verts_to_consider:
#print('no more verts_to_consider')
pass
CoDEmanX
committed
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)
CoDEmanX
committed
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])
CoDEmanX
committed
CoDEmanX
committed
def move_verts(vert_loop, other_vert_loop):
CoDEmanX
committed
### 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)
CoDEmanX
committed
vert_opposite = other_vert_loop[i]
edges_select_opposite = selected_edges(vert_opposite)
edges_unselect_opposite = selected_edges(vert_opposite, invert=True)
CoDEmanX
committed
### 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
CoDEmanX
committed
### 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
CoDEmanX
committed
### 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
CoDEmanX
committed
CoDEmanX
committed
### Moving ###
def move(offsets):
#print('\nMOVING VERTS')
for vert in offsets:
vert.co += offsets[vert] * self.distance
CoDEmanX
committed
offsets = calc_offsets()
move(offsets)
CoDEmanX
committed
def generate_new_geo(vert_loop, other_vert_loop):
#print('\nGENERATING NEW GEOMETRY')
CoDEmanX
committed
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
CoDEmanX
committed
bpy.ops.mesh.edge_face_add()
#####################################################################################
#####################################################################################
#####################################################################################
CoDEmanX
committed
bme = bmesh.from_edit_mesh(context.object.data)
### COLLECT EDGE LOOPS ###
e_loops = collect_edge_loops(bme)
CoDEmanX
committed
CoDEmanX
committed
#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
CoDEmanX
committed
### SPLITTING OF EDGES
other_vert_loop, new_loop = split_edge_loop(e_loop)
if closed:
e_loop = new_loop
CoDEmanX
committed
### MOVE RIPPED VERTS ###
move_verts(e_loop, other_vert_loop)
CoDEmanX
committed
### GENERATE NEW GEOMETRY ###
if self.generate_geo:
generate_new_geo(e_loop, other_vert_loop)
CoDEmanX
committed
CoDEmanX
committed
###########################################################################
# 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")
CoDEmanX
committed
even = BoolProperty(
name='even',
default=True,
description='keep 90 degrees angles straight')
CoDEmanX
committed
generate_geo = BoolProperty(
name='Generate Geo',
default=True,
description='Fill edgering with faces')
CoDEmanX
committed
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')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
CoDEmanX
committed
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)
CoDEmanX
committed
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
@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()