4
Name: 'Bridge/Skin/Loft'
7
Tooltip: 'Select 2 or more vert loops, then run this script'
10
__author__ = "Campbell Barton AKA Ideasman"
11
__url__ = ["http://members.iinet.net.au/~cpbarton/ideasman/", "blender", "elysiun"]
12
__version__ = "1.1 2005/06/13"
15
With this script vertex loops can be skinned: faces are created to connect the
16
selected loops of vertices.
20
In mesh Edit mode select the vertices of the loops (closed paths / curves of
21
vertices: circles, for example) that should be skinned, then run this script.
22
A pop-up will provide further options.
26
If the results of a method chosen from the pop-up are not adequate, undo and try one of the others.
30
# $Id: skin.py,v 1.3 2005/06/13 17:21:30 ianwill Exp $
32
# --------------------------------------------------------------------------
33
# Skin Selected edges 1.0 By Campbell Barton (AKA Ideasman)
34
# --------------------------------------------------------------------------
35
# ***** BEGIN GPL LICENSE BLOCK *****
37
# This program is free software; you can redistribute it and/or
38
# modify it under the terms of the GNU General Public License
39
# as published by the Free Software Foundation; either version 2
40
# of the License, or (at your option) any later version.
42
# This program is distributed in the hope that it will be useful,
43
# but WITHOUT ANY WARRANTY; without even the implied warranty of
44
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45
# GNU General Public License for more details.
47
# You should have received a copy of the GNU General Public License
48
# along with this program; if not, write to the Free Software Foundation,
49
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
51
# ***** END GPL LICENCE BLOCK *****
52
# --------------------------------------------------------------------------
56
# Made by Ideasman/Campbell 2004/04/25 - ideasman@linuxmail.org
64
choice = Draw.PupMenu(\
65
'Loft-loop - shortest edge method|\
66
Loft-loop - even method|\
67
Loft-segment - shortest edge|\
68
Loft-segment - even method')
87
return Mathutils.Vector([v1[0]-v2[0], v1[1] - v2[1], v1[2] - v2[2]]).length
90
def clamp(max, number):
95
#=============================================================#
96
# List func that takes the last item and adds it to the front #
97
#=============================================================#
101
#=================================================================#
102
# Recieve a list of locs: [x,y,z] and return the average location #
103
#=================================================================#
104
def averageLocation(locList):
108
for coordIdx in [0,1,2]:
110
# Add all the values from 1 of the 3 coords at the avLoc.
112
avLoc[coordIdx] += loc[coordIdx]
114
avLoc[coordIdx] = avLoc[coordIdx] / len(locList)
119
#=============================#
120
# Blender functions/shortcuts #
121
#=============================#
123
Draw.PupMenu('ERROR%t|'+str)
125
# Returns a new face that has the same properties as the origional face
126
# With no verts though
128
newFace = NMesh.Face()
129
# Copy some generic properties
130
newFace.mode = face.mode
131
if face.image != None:
132
newFace.image = face.image
133
newFace.flag = face.flag
134
newFace.mat = face.mat
135
newFace.smooth = face.smooth
138
#=============================================#
139
# Find a selected vert that 2 faces share. #
140
#=============================================#
141
def selVertBetween2Faces(face1, face2):
149
#=======================================================#
150
# Measure the total distance between all the edges in #
152
#=======================================================#
153
def measureVloop(mesh, v1loop, v2loop, surplusFaces, bestSoFar):
156
# Rotate the vertloops to cycle through each pair.
157
# of faces to compate the distance between the 2 poins
158
for ii in range(len(v1loop)):
159
if ii not in surplusFaces:
162
while v2clampii >= len(v2loop):
163
v2clampii -= len(v2loop)
166
V1 = selVertBetween2Faces(mesh.faces[v1loop[ii-1]], mesh.faces[v1loop[ii]])
167
V2 = selVertBetween2Faces(mesh.faces[v2loop[v2clampii-1]], mesh.faces[v2loop[v2clampii]])
169
totalDist += measure(V1, V2)
170
# Bail out early if not an improvement on previously measured.
171
if bestSoFar != None and totalDist > bestSoFar:
174
#selVertBetween2Faces(mesh.faces[v2loop[0]], mesh.faces[v2loop[1]])
177
# Remove the shortest edge from a vert loop
178
def removeSmallestFace(mesh, vloop):
183
for v in mesh.faces[fIdx].v:
187
dist = measure(vSelLs[0].co, vSelLs[1].co)
189
if bestDistSoFar == None:
192
elif dist < bestDistSoFar:
196
# Return the smallest face index of the vloop that was sent
200
#=============================================#
201
# Take 2 vert loops and skin them #
202
#=============================================#
203
def skinVertLoops(mesh, v1loop, v2loop):
206
#=============================================#
207
# Handle uneven vert loops, this is tricky #
208
#=============================================#
209
# Reorder so v1loop is always the biggest
210
if len(v1loop) < len(v2loop):
211
v1loop, v2loop = v2loop, v1loop
213
# Work out if the vert loops are equel or not, if not remove the extra faces from the larger
215
tempv1loop = v1loop[:] # strip faces off this one, use it to keep track of which we have taken faces from.
216
if len(v1loop) > len(v2loop):
221
faceStepping = len( v1loop) / len(v2loop)
222
while len(v1loop) - len(surplusFaces) > len(v2loop):
223
remIdx += faceStepping
224
surplusFaces.append(tempv1loop[ clamp(len(tempv1loop),remIdx) ])
225
tempv1loop.remove(surplusFaces[-1])
229
while len(v1loop) - len(surplusFaces) > len(v2loop):
230
surplusFaces.append(removeSmallestFace(mesh, tempv1loop))
231
tempv1loop.remove(surplusFaces[-1])
236
v2loop = optimizeLoopOrdedShortEdge(mesh, v1loop, v2loop, surplusFaces)
239
lenVloop = len(v1loop)
240
lenSupFaces = len(surplusFaces)
243
while fIdx < lenVloop:
245
face = copyFace( mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]] )
247
if v1loop[fIdx] in surplusFaces:
248
# Draw a try, this face does not catch with an edge.
249
# So we must draw a tri and wedge it in.
251
# Copy old faces properties
253
face.v.append( selVertBetween2Faces(\
254
mesh.faces[v1loop[clamp(lenVloop, fIdx)]],\
255
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]]) )
257
face.v.append( selVertBetween2Faces(\
258
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]],\
259
mesh.faces[v1loop[clamp(lenVloop, fIdx+2)]]) )
261
#face.v.append( selVertBetween2Faces(\
262
#mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset +1 ))]],\
263
#mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset + 2))]]) )
265
face.v.append( selVertBetween2Faces(\
266
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset))]],\
267
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, fIdx - offset + 1)]]) )
269
mesh.faces.append(face)
271
# We need offset to work out how much smaller v2loop is at this current index.
276
# Draw a normal quad between the 2 edges/faces
278
face.v.append( selVertBetween2Faces(\
279
mesh.faces[v1loop[clamp(lenVloop, fIdx)]],\
280
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]]) )
282
face.v.append( selVertBetween2Faces(\
283
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]],\
284
mesh.faces[v1loop[clamp(lenVloop, fIdx+2)]]) )
286
face.v.append( selVertBetween2Faces(\
287
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset +1 ))]],\
288
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset + 2))]]) )
290
face.v.append( selVertBetween2Faces(\
291
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset))]],\
292
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, fIdx - offset + 1)]]) )
294
mesh.faces.append(face)
302
#=======================================================#
303
# Takes a face and returns the number of selected verts #
304
#=======================================================#
315
#================================================================#
316
# This function takes a face and returns its selected vert loop #
317
# it returns a list of face indicies
318
#================================================================#
319
def vertLoop(mesh, startFaceIdx, fIgLs): # fIgLs is a list of faces to ignore.
320
# Here we store the faces indicies that
321
# are a part of the first vertex loop
322
vertLoopLs = [startFaceIdx]
326
# this keeps the face loop going until its told to stop,
327
# If the face loop does not find an adjacent face then the vert loop has been compleated
330
# Get my selected verts for the active face/edge.
332
for v in mesh.faces[vertLoopLs[-1]].v:
336
while fIdx < len(mesh.faces) and restart:
337
# Not already added to the vert list
338
if fIdx not in fIgLs + vertLoopLs:
339
# Has 2 verts selected
340
if faceVSel(mesh.faces[fIdx]) > 1:
341
# Now we need to find if any of the selected verts
342
# are shared with our active face. (are we next to ActiveFace)
343
for v in mesh.faces[fIdx].v:
345
vertLoopLs.append(fIdx)
346
restart = 0 # restart the face loop.
356
#================================================================#
357
# Now we work out the optimum order to 'skin' the 2 vert loops #
358
# by measuring the total distance of all edges created, #
359
# test this for every possible series of joins #
360
# and find the shortest, Once this is done the #
361
# shortest dist can be skinned. #
362
# returns only the 2nd-reordered vert loop #
363
#================================================================#
364
def optimizeLoopOrded(mesh, v1loop, v2loop):
367
# Measure the dist, ii is just a counter
368
for ii in range(len(v1loop)):
370
# Loop twice , Once for the forward test, and another for the revearsed
371
for iii in [None, None]:
372
dist = measureVloop(mesh, v1loop, v2loop, bestSoFar)
373
# Initialize the Best distance recorded
374
if bestSoFar == None or dist < bestSoFar:
376
bestv2Loop = v2loop[:]
378
# We might have got the vert loop backwards, try the other way
384
#================================================================#
385
# Now we work out the optimum order to 'skin' the 2 vert loops #
386
# by measuring the total distance of all edges created, #
387
# test this for every possible series of joins #
388
# and find the shortest, Once this is done the #
389
# shortest dist can be skinned. #
390
# returns only the 2nd-reordered vert loop #
391
#================================================================#
392
def optimizeLoopOrdedShortEdge(mesh, v1loop, v2loop, surplusFaces):
395
# Measure the dist, ii is just a counter
396
for ii in range(len(v2loop)):
398
# Loop twice , Once for the forward test, and another for the revearsed
399
for iii in [None, None]:
400
dist = measureVloop(mesh, v1loop, v2loop, surplusFaces, bestSoFar)
402
# Initialize the Best distance recorded
403
if bestSoFar == None or dist < bestSoFar:
405
bestv2Loop = v2loop[:]
408
# We might have got the vert loop backwards, try the other way
410
#v2loop = listRotate(v2loop)
412
print 'best so far ', bestSoFar
416
#==============================#
417
# Find our vert loop list #
418
#==============================#
419
# Find a face with 2 verts selected,
420
#this will be the first face in out vert loop
421
def findVertLoop(mesh, fIgLs): # fIgLs is a list of faces to ignore.
426
while fIdx < len(mesh.faces):
427
if fIdx not in fIgLs:
428
# Do we have an edge?
429
if faceVSel(mesh.faces[fIdx]) > 1:
430
# THIS IS THE STARTING FACE.
435
# Here we access the function that generates the real vert loop
436
if startFaceIdx != None:
437
return vertLoop(mesh, startFaceIdx, fIgLs)
439
# We are out'a vert loops, return a None,
442
#===================================#
443
# Get the average loc of a vertloop #
444
# This is used when working out the #
445
# order to loft an object #
446
#===================================#
447
def vLoopAverageLoc(mesh, vertLoop):
448
locList = [] # List of vert locations
451
while fIdx < len(mesh.faces):
453
for v in mesh.faces[fIdx].v:
458
return averageLocation(locList)
462
#=================================================#
463
# Vert loop group functions
465
def getAllVertLoops(mesh):
466
# Make a chain of vert loops.
467
fIgLs = [] # List of faces to ignore
468
allVLoops = [findVertLoop(mesh, fIgLs)]
469
while allVLoops[-1] != None:
471
# In future ignore all faces in this vert loop
472
fIgLs += allVLoops[-1]
474
# Add the new vert loop to the list
475
allVLoops.append( findVertLoop(mesh, fIgLs) )
477
return allVLoops[:-1] # Remove the last Value- None.
480
def reorderCircularVLoops(mesh, allVLoops):
481
# Now get a location for each vert loop.
483
for vLoop in allVLoops:
484
allVertLoopLocs.append( vLoopAverageLoc(mesh, vLoop) )
486
# We need to find the longest distance between 2 vert loops so we can
487
reorderedVLoopLocs = []
489
# Start with this one, then find the next closest.
490
# in doing this make a new list called reorderedVloop
492
reorderedVloopIdx = [currentVLoop]
493
newOrderVLoops = [allVLoops[0]] # This is a re-ordered allVLoops
494
while len(reorderedVloopIdx) != len(allVLoops):
497
for vLoopIdx in range(len(allVLoops)):
498
if vLoopIdx not in reorderedVloopIdx + [currentVLoop]:
499
if bestSoFar == None:
500
bestSoFar = measure( allVertLoopLocs[vLoopIdx], allVertLoopLocs[currentVLoop] )
501
bestVIdxSoFar = vLoopIdx
503
newDist = measure( allVertLoopLocs[vLoopIdx], allVertLoopLocs[currentVLoop] )
504
if newDist < bestSoFar:
506
bestVIdxSoFar = vLoopIdx
508
reorderedVloopIdx.append(bestVIdxSoFar)
509
reorderedVLoopLocs.append(allVertLoopLocs[bestVIdxSoFar])
510
newOrderVLoops.append( allVLoops[bestVIdxSoFar] )
512
# Start looking for the next best fit
513
currentVLoop = bestVIdxSoFar
515
# This is not the locicle place to put this but its convieneint.
516
# Here we find the 2 vert loops that are most far apart
517
# We use this to work out which 2 vert loops not to skin when making an open loft.
519
# Longest measured so far - 0 dummy.
521
while vLoopIdx < len(reorderedVLoopLocs):
523
# Skin back to the start if needs be, becuase this is a crcular loft
524
toSkin2 = vLoopIdx + 1
525
if toSkin2 == len(reorderedVLoopLocs):
528
newDist = measure( reorderedVLoopLocs[vLoopIdx], reorderedVLoopLocs[toSkin2] )
530
if newDist >= bestSoFar:
532
vLoopIdxNotToSkin = vLoopIdx + 1
536
return newOrderVLoops, vLoopIdxNotToSkin
539
is_editmode = Window.EditMode()
540
if is_editmode: Window.EditMode(0)
542
# Get a mesh and raise errors if we cant
546
elif len(Object.GetSelected()) > 0:
547
if Object.GetSelected()[0].getType() == 'Mesh':
548
mesh = Object.GetSelected()[0].getData()
550
error('please select a mesh')
552
error('no mesh object selected')
557
allVLoops = getAllVertLoops(mesh)
559
# Re order the vert loops
560
allVLoops, vLoopIdxNotToSkin = reorderCircularVLoops(mesh, allVLoops)
563
while vloopIdx < len(allVLoops):
564
#print range(len(allVLoops) )
566
#print allVLoops[vloopIdx]
568
# Skin back to the start if needs be, becuase this is a crcular loft
569
toSkin2 = vloopIdx + 1
570
if toSkin2 == len(allVLoops):
573
# Circular loft or not?
574
if arg[0] == 'B': # B for open
575
if vloopIdx != vLoopIdxNotToSkin:
576
mesh = skinVertLoops(mesh, allVLoops[vloopIdx], allVLoops[toSkin2])
577
elif arg[0] == 'A': # A for closed
578
mesh = skinVertLoops(mesh, allVLoops[vloopIdx], allVLoops[toSkin2])
582
mesh.update(1,(mesh.edges != []),0)
584
if is_editmode: Window.EditMode(1)
586
print "skinning time: %.2f" % (sys.time() - time1)