forked from bartvdbraak/blender
de567cd0cc
- Campbell Barton updated his obj exporter and skin (bridge/skin/loft) scripts. Thanks.
587 lines
17 KiB
Python
587 lines
17 KiB
Python
#!BPY
|
|
|
|
"""
|
|
Name: 'Bridge/Skin/Loft'
|
|
Blender: 234
|
|
Group: 'Mesh'
|
|
Tooltip: 'Select 2 or more vert loops, then run this script'
|
|
"""
|
|
|
|
__author__ = "Campbell Barton AKA Ideasman"
|
|
__url__ = ["http://members.iinet.net.au/~cpbarton/ideasman/", "blender", "elysiun"]
|
|
__version__ = "1.1 2005/06/13"
|
|
|
|
__bpydoc__ = """\
|
|
With this script vertex loops can be skinned: faces are created to connect the
|
|
selected loops of vertices.
|
|
|
|
Usage:
|
|
|
|
In mesh Edit mode select the vertices of the loops (closed paths / curves of
|
|
vertices: circles, for example) that should be skinned, then run this script.
|
|
A pop-up will provide further options.
|
|
|
|
Notes:
|
|
|
|
If the results of a method chosen from the pop-up are not adequate, undo and try one of the others.
|
|
"""
|
|
|
|
|
|
# $Id$
|
|
#
|
|
# --------------------------------------------------------------------------
|
|
# Skin Selected edges 1.0 By Campbell Barton (AKA Ideasman)
|
|
# --------------------------------------------------------------------------
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
|
|
# Made by Ideasman/Campbell 2004/04/25 - ideasman@linuxmail.org
|
|
|
|
import Blender
|
|
from Blender import *
|
|
import math
|
|
from math import *
|
|
|
|
|
|
choice = Draw.PupMenu(\
|
|
'Loft-loop - shortest edge method|\
|
|
Loft-loop - even method|\
|
|
Loft-segment - shortest edge|\
|
|
Loft-segment - even method')
|
|
|
|
if choice == 1:
|
|
arg='A1'
|
|
elif choice == 2:
|
|
arg='A2'
|
|
elif choice == 3:
|
|
arg='B1'
|
|
elif choice == 4:
|
|
arg='B2'
|
|
|
|
|
|
|
|
#================#
|
|
# Math functions #
|
|
#================#
|
|
|
|
# Measure 2 points
|
|
def measure(v1, v2):
|
|
return Mathutils.Vector([v1[0]-v2[0], v1[1] - v2[1], v1[2] - v2[2]]).length
|
|
|
|
# Clamp
|
|
def clamp(max, number):
|
|
while number >= max:
|
|
number = number - max
|
|
return number
|
|
|
|
#=============================================================#
|
|
# List func that takes the last item and adds it to the front #
|
|
#=============================================================#
|
|
def listRotate(ls):
|
|
ls.append(ls.pop(0))
|
|
|
|
#=================================================================#
|
|
# Recieve a list of locs: [x,y,z] and return the average location #
|
|
#=================================================================#
|
|
def averageLocation(locList):
|
|
avLoc = [0,0,0]
|
|
|
|
# Loop through x/y/z
|
|
for coordIdx in [0,1,2]:
|
|
|
|
# Add all the values from 1 of the 3 coords at the avLoc.
|
|
for loc in locList:
|
|
avLoc[coordIdx] += loc[coordIdx]
|
|
|
|
avLoc[coordIdx] = avLoc[coordIdx] / len(locList)
|
|
return avLoc
|
|
|
|
|
|
|
|
#=============================#
|
|
# Blender functions/shortcuts #
|
|
#=============================#
|
|
def error(str):
|
|
Draw.PupMenu('ERROR%t|'+str)
|
|
|
|
# Returns a new face that has the same properties as the origional face
|
|
# With no verts though
|
|
def copyFace(face):
|
|
newFace = NMesh.Face()
|
|
# Copy some generic properties
|
|
newFace.mode = face.mode
|
|
if face.image != None:
|
|
newFace.image = face.image
|
|
newFace.flag = face.flag
|
|
newFace.mat = face.mat
|
|
newFace.smooth = face.smooth
|
|
return newFace
|
|
|
|
#=============================================#
|
|
# Find a selected vert that 2 faces share. #
|
|
#=============================================#
|
|
def selVertBetween2Faces(face1, face2):
|
|
for v1 in face1.v:
|
|
if v1.sel:
|
|
for v2 in face2.v:
|
|
if v1 == v2:
|
|
return v1
|
|
|
|
|
|
#=======================================================#
|
|
# Measure the total distance between all the edges in #
|
|
# 2 vertex loops #
|
|
#=======================================================#
|
|
def measureVloop(mesh, v1loop, v2loop, surplusFaces, bestSoFar):
|
|
totalDist = 0
|
|
|
|
# Rotate the vertloops to cycle through each pair.
|
|
# of faces to compate the distance between the 2 poins
|
|
for ii in range(len(v1loop)):
|
|
if ii not in surplusFaces:
|
|
# Clamp
|
|
v2clampii = ii
|
|
while v2clampii >= len(v2loop):
|
|
v2clampii -= len(v2loop)
|
|
print v2clampii
|
|
|
|
V1 = selVertBetween2Faces(mesh.faces[v1loop[ii-1]], mesh.faces[v1loop[ii]])
|
|
V2 = selVertBetween2Faces(mesh.faces[v2loop[v2clampii-1]], mesh.faces[v2loop[v2clampii]])
|
|
|
|
totalDist += measure(V1, V2)
|
|
# Bail out early if not an improvement on previously measured.
|
|
if bestSoFar != None and totalDist > bestSoFar:
|
|
return totalDist
|
|
|
|
#selVertBetween2Faces(mesh.faces[v2loop[0]], mesh.faces[v2loop[1]])
|
|
return totalDist
|
|
|
|
# Remove the shortest edge from a vert loop
|
|
def removeSmallestFace(mesh, vloop):
|
|
bestDistSoFar = None
|
|
bestFIdxSoFar = None
|
|
for fIdx in vloop:
|
|
vSelLs = []
|
|
for v in mesh.faces[fIdx].v:
|
|
if v.sel:
|
|
vSelLs.append(v)
|
|
|
|
dist = measure(vSelLs[0].co, vSelLs[1].co)
|
|
|
|
if bestDistSoFar == None:
|
|
bestDistSoFar = dist
|
|
bestFIdxSoFar = fIdx
|
|
elif dist < bestDistSoFar:
|
|
bestDistSoFar = dist
|
|
bestFIdxSoFar = fIdx
|
|
|
|
# Return the smallest face index of the vloop that was sent
|
|
return bestFIdxSoFar
|
|
|
|
|
|
#=============================================#
|
|
# Take 2 vert loops and skin them #
|
|
#=============================================#
|
|
def skinVertLoops(mesh, v1loop, v2loop):
|
|
|
|
|
|
#=============================================#
|
|
# Handle uneven vert loops, this is tricky #
|
|
#=============================================#
|
|
# Reorder so v1loop is always the biggest
|
|
if len(v1loop) < len(v2loop):
|
|
v1loop, v2loop = v2loop, v1loop
|
|
|
|
# Work out if the vert loops are equel or not, if not remove the extra faces from the larger
|
|
surplusFaces = []
|
|
tempv1loop = v1loop[:] # strip faces off this one, use it to keep track of which we have taken faces from.
|
|
if len(v1loop) > len(v2loop):
|
|
|
|
# Even face method.
|
|
if arg[1] == '2':
|
|
remIdx = 0
|
|
faceStepping = len( v1loop) / len(v2loop)
|
|
while len(v1loop) - len(surplusFaces) > len(v2loop):
|
|
remIdx += faceStepping
|
|
surplusFaces.append(tempv1loop[ clamp(len(tempv1loop),remIdx) ])
|
|
tempv1loop.remove(surplusFaces[-1])
|
|
|
|
# Shortest face
|
|
elif arg[1] == '1':
|
|
while len(v1loop) - len(surplusFaces) > len(v2loop):
|
|
surplusFaces.append(removeSmallestFace(mesh, tempv1loop))
|
|
tempv1loop.remove(surplusFaces[-1])
|
|
|
|
|
|
tempv1loop = None
|
|
|
|
v2loop = optimizeLoopOrdedShortEdge(mesh, v1loop, v2loop, surplusFaces)
|
|
|
|
# make Faces from
|
|
lenVloop = len(v1loop)
|
|
lenSupFaces = len(surplusFaces)
|
|
fIdx = 0
|
|
offset = 0
|
|
while fIdx < lenVloop:
|
|
|
|
face = copyFace( mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]] )
|
|
|
|
if v1loop[fIdx] in surplusFaces:
|
|
# Draw a try, this face does not catch with an edge.
|
|
# So we must draw a tri and wedge it in.
|
|
|
|
# Copy old faces properties
|
|
|
|
face.v.append( selVertBetween2Faces(\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx)]],\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]]) )
|
|
|
|
face.v.append( selVertBetween2Faces(\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]],\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx+2)]]) )
|
|
|
|
#face.v.append( selVertBetween2Faces(\
|
|
#mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset +1 ))]],\
|
|
#mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset + 2))]]) )
|
|
|
|
face.v.append( selVertBetween2Faces(\
|
|
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset))]],\
|
|
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, fIdx - offset + 1)]]) )
|
|
|
|
mesh.faces.append(face)
|
|
|
|
# We need offset to work out how much smaller v2loop is at this current index.
|
|
offset+=1
|
|
|
|
|
|
else:
|
|
# Draw a normal quad between the 2 edges/faces
|
|
|
|
face.v.append( selVertBetween2Faces(\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx)]],\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]]) )
|
|
|
|
face.v.append( selVertBetween2Faces(\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx+1)]],\
|
|
mesh.faces[v1loop[clamp(lenVloop, fIdx+2)]]) )
|
|
|
|
face.v.append( selVertBetween2Faces(\
|
|
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset +1 ))]],\
|
|
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset + 2))]]) )
|
|
|
|
face.v.append( selVertBetween2Faces(\
|
|
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, (fIdx - offset))]],\
|
|
mesh.faces[v2loop[clamp(lenVloop - lenSupFaces, fIdx - offset + 1)]]) )
|
|
|
|
mesh.faces.append(face)
|
|
|
|
fIdx +=1
|
|
|
|
return mesh
|
|
|
|
|
|
|
|
#=======================================================#
|
|
# Takes a face and returns the number of selected verts #
|
|
#=======================================================#
|
|
def faceVSel(face):
|
|
vSel = 0
|
|
for v in face.v:
|
|
if v.sel:
|
|
vSel +=1
|
|
return vSel
|
|
|
|
|
|
|
|
|
|
#================================================================#
|
|
# This function takes a face and returns its selected vert loop #
|
|
# it returns a list of face indicies
|
|
#================================================================#
|
|
def vertLoop(mesh, startFaceIdx, fIgLs): # fIgLs is a list of faces to ignore.
|
|
# Here we store the faces indicies that
|
|
# are a part of the first vertex loop
|
|
vertLoopLs = [startFaceIdx]
|
|
|
|
restart = 0
|
|
while restart == 0:
|
|
# this keeps the face loop going until its told to stop,
|
|
# If the face loop does not find an adjacent face then the vert loop has been compleated
|
|
restart = 1
|
|
|
|
# Get my selected verts for the active face/edge.
|
|
selVerts = []
|
|
for v in mesh.faces[vertLoopLs[-1]].v:
|
|
selVerts.append(v)
|
|
|
|
fIdx = 0
|
|
while fIdx < len(mesh.faces) and restart:
|
|
# Not already added to the vert list
|
|
if fIdx not in fIgLs + vertLoopLs:
|
|
# Has 2 verts selected
|
|
if faceVSel(mesh.faces[fIdx]) > 1:
|
|
# Now we need to find if any of the selected verts
|
|
# are shared with our active face. (are we next to ActiveFace)
|
|
for v in mesh.faces[fIdx].v:
|
|
if v in selVerts:
|
|
vertLoopLs.append(fIdx)
|
|
restart = 0 # restart the face loop.
|
|
break
|
|
|
|
fIdx +=1
|
|
|
|
return vertLoopLs
|
|
|
|
|
|
|
|
|
|
#================================================================#
|
|
# Now we work out the optimum order to 'skin' the 2 vert loops #
|
|
# by measuring the total distance of all edges created, #
|
|
# test this for every possible series of joins #
|
|
# and find the shortest, Once this is done the #
|
|
# shortest dist can be skinned. #
|
|
# returns only the 2nd-reordered vert loop #
|
|
#================================================================#
|
|
def optimizeLoopOrded(mesh, v1loop, v2loop):
|
|
bestSoFar = None
|
|
|
|
# Measure the dist, ii is just a counter
|
|
for ii in range(len(v1loop)):
|
|
|
|
# Loop twice , Once for the forward test, and another for the revearsed
|
|
for iii in [None, None]:
|
|
dist = measureVloop(mesh, v1loop, v2loop, bestSoFar)
|
|
# Initialize the Best distance recorded
|
|
if bestSoFar == None or dist < bestSoFar:
|
|
bestSoFar = dist
|
|
bestv2Loop = v2loop[:]
|
|
|
|
# We might have got the vert loop backwards, try the other way
|
|
v2loop.reverse()
|
|
listRotate(v2loop)
|
|
return bestv2Loop
|
|
|
|
|
|
#================================================================#
|
|
# Now we work out the optimum order to 'skin' the 2 vert loops #
|
|
# by measuring the total distance of all edges created, #
|
|
# test this for every possible series of joins #
|
|
# and find the shortest, Once this is done the #
|
|
# shortest dist can be skinned. #
|
|
# returns only the 2nd-reordered vert loop #
|
|
#================================================================#
|
|
def optimizeLoopOrdedShortEdge(mesh, v1loop, v2loop, surplusFaces):
|
|
bestSoFar = None
|
|
|
|
# Measure the dist, ii is just a counter
|
|
for ii in range(len(v2loop)):
|
|
|
|
# Loop twice , Once for the forward test, and another for the revearsed
|
|
for iii in [None, None]:
|
|
dist = measureVloop(mesh, v1loop, v2loop, surplusFaces, bestSoFar)
|
|
print 'dist', dist
|
|
# Initialize the Best distance recorded
|
|
if bestSoFar == None or dist < bestSoFar:
|
|
bestSoFar = dist
|
|
bestv2Loop = v2loop[:]
|
|
|
|
|
|
# We might have got the vert loop backwards, try the other way
|
|
v2loop.reverse()
|
|
#v2loop = listRotate(v2loop)
|
|
listRotate(v2loop)
|
|
print 'best so far ', bestSoFar
|
|
return bestv2Loop
|
|
|
|
|
|
#==============================#
|
|
# Find our vert loop list #
|
|
#==============================#
|
|
# Find a face with 2 verts selected,
|
|
#this will be the first face in out vert loop
|
|
def findVertLoop(mesh, fIgLs): # fIgLs is a list of faces to ignore.
|
|
|
|
startFaceIdx = None
|
|
|
|
fIdx = 0
|
|
while fIdx < len(mesh.faces):
|
|
if fIdx not in fIgLs:
|
|
# Do we have an edge?
|
|
if faceVSel(mesh.faces[fIdx]) > 1:
|
|
# THIS IS THE STARTING FACE.
|
|
startFaceIdx = fIdx
|
|
break
|
|
fIdx+=1
|
|
|
|
# Here we access the function that generates the real vert loop
|
|
if startFaceIdx != None:
|
|
return vertLoop(mesh, startFaceIdx, fIgLs)
|
|
else:
|
|
# We are out'a vert loops, return a None,
|
|
return None
|
|
|
|
#===================================#
|
|
# Get the average loc of a vertloop #
|
|
# This is used when working out the #
|
|
# order to loft an object #
|
|
#===================================#
|
|
def vLoopAverageLoc(mesh, vertLoop):
|
|
locList = [] # List of vert locations
|
|
|
|
fIdx = 0
|
|
while fIdx < len(mesh.faces):
|
|
if fIdx in vertLoop:
|
|
for v in mesh.faces[fIdx].v:
|
|
if v.sel:
|
|
locList.append(v.co)
|
|
fIdx+=1
|
|
|
|
return averageLocation(locList)
|
|
|
|
|
|
|
|
#=================================================#
|
|
# Vert loop group functions
|
|
|
|
def getAllVertLoops(mesh):
|
|
# Make a chain of vert loops.
|
|
fIgLs = [] # List of faces to ignore
|
|
allVLoops = [findVertLoop(mesh, fIgLs)]
|
|
while allVLoops[-1] != None:
|
|
|
|
# In future ignore all faces in this vert loop
|
|
fIgLs += allVLoops[-1]
|
|
|
|
# Add the new vert loop to the list
|
|
allVLoops.append( findVertLoop(mesh, fIgLs) )
|
|
|
|
return allVLoops[:-1] # Remove the last Value- None.
|
|
|
|
|
|
def reorderCircularVLoops(mesh, allVLoops):
|
|
# Now get a location for each vert loop.
|
|
allVertLoopLocs = []
|
|
for vLoop in allVLoops:
|
|
allVertLoopLocs.append( vLoopAverageLoc(mesh, vLoop) )
|
|
|
|
# We need to find the longest distance between 2 vert loops so we can
|
|
reorderedVLoopLocs = []
|
|
|
|
# Start with this one, then find the next closest.
|
|
# in doing this make a new list called reorderedVloop
|
|
currentVLoop = 0
|
|
reorderedVloopIdx = [currentVLoop]
|
|
newOrderVLoops = [allVLoops[0]] # This is a re-ordered allVLoops
|
|
while len(reorderedVloopIdx) != len(allVLoops):
|
|
bestSoFar = None
|
|
bestVIdxSoFar = None
|
|
for vLoopIdx in range(len(allVLoops)):
|
|
if vLoopIdx not in reorderedVloopIdx + [currentVLoop]:
|
|
if bestSoFar == None:
|
|
bestSoFar = measure( allVertLoopLocs[vLoopIdx], allVertLoopLocs[currentVLoop] )
|
|
bestVIdxSoFar = vLoopIdx
|
|
else:
|
|
newDist = measure( allVertLoopLocs[vLoopIdx], allVertLoopLocs[currentVLoop] )
|
|
if newDist < bestSoFar:
|
|
bestSoFar = newDist
|
|
bestVIdxSoFar = vLoopIdx
|
|
|
|
reorderedVloopIdx.append(bestVIdxSoFar)
|
|
reorderedVLoopLocs.append(allVertLoopLocs[bestVIdxSoFar])
|
|
newOrderVLoops.append( allVLoops[bestVIdxSoFar] )
|
|
|
|
# Start looking for the next best fit
|
|
currentVLoop = bestVIdxSoFar
|
|
|
|
# This is not the locicle place to put this but its convieneint.
|
|
# Here we find the 2 vert loops that are most far apart
|
|
# We use this to work out which 2 vert loops not to skin when making an open loft.
|
|
vLoopIdx = 0
|
|
# Longest measured so far - 0 dummy.
|
|
bestSoFar = 0
|
|
while vLoopIdx < len(reorderedVLoopLocs):
|
|
|
|
# Skin back to the start if needs be, becuase this is a crcular loft
|
|
toSkin2 = vLoopIdx + 1
|
|
if toSkin2 == len(reorderedVLoopLocs):
|
|
toSkin2 = 0
|
|
|
|
newDist = measure( reorderedVLoopLocs[vLoopIdx], reorderedVLoopLocs[toSkin2] )
|
|
|
|
if newDist >= bestSoFar:
|
|
bestSoFar = newDist
|
|
vLoopIdxNotToSkin = vLoopIdx + 1
|
|
|
|
vLoopIdx +=1
|
|
|
|
return newOrderVLoops, vLoopIdxNotToSkin
|
|
|
|
|
|
is_editmode = Window.EditMode()
|
|
if is_editmode: Window.EditMode(0)
|
|
|
|
# Get a mesh and raise errors if we cant
|
|
mesh = None
|
|
if choice == -1:
|
|
pass
|
|
elif len(Object.GetSelected()) > 0:
|
|
if Object.GetSelected()[0].getType() == 'Mesh':
|
|
mesh = Object.GetSelected()[0].getData()
|
|
else:
|
|
error('please select a mesh')
|
|
else:
|
|
error('no mesh object selected')
|
|
|
|
time1 = sys.time()
|
|
if mesh != None:
|
|
Window.WaitCursor(1)
|
|
allVLoops = getAllVertLoops(mesh)
|
|
|
|
# Re order the vert loops
|
|
allVLoops, vLoopIdxNotToSkin = reorderCircularVLoops(mesh, allVLoops)
|
|
|
|
vloopIdx = 0
|
|
while vloopIdx < len(allVLoops):
|
|
#print range(len(allVLoops) )
|
|
#print vloopIdx
|
|
#print allVLoops[vloopIdx]
|
|
|
|
# Skin back to the start if needs be, becuase this is a crcular loft
|
|
toSkin2 = vloopIdx + 1
|
|
if toSkin2 == len(allVLoops):
|
|
toSkin2 = 0
|
|
|
|
# Circular loft or not?
|
|
if arg[0] == 'B': # B for open
|
|
if vloopIdx != vLoopIdxNotToSkin:
|
|
mesh = skinVertLoops(mesh, allVLoops[vloopIdx], allVLoops[toSkin2])
|
|
elif arg[0] == 'A': # A for closed
|
|
mesh = skinVertLoops(mesh, allVLoops[vloopIdx], allVLoops[toSkin2])
|
|
|
|
vloopIdx +=1
|
|
|
|
mesh.update(1,(mesh.edges != []),0)
|
|
|
|
if is_editmode: Window.EditMode(1)
|
|
Window.WaitCursor(0)
|
|
print "skinning time: %.2f" % (sys.time() - time1)
|