-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmesh_insert_edge_ring.py
377 lines (317 loc) · 13.6 KB
/
mesh_insert_edge_ring.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
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
# 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()