#!BPY """ Name: 'Skin Two Vert-loops / Loft Multiple' Blender: 234 Group: 'Mesh' Submenu: 'Loft-loop - shortest edge method' A1 Submenu: 'Loft-loop - even method' A2 Submenu: 'Loft-segment - shortest edge' B1 Submenu: 'Loft-segment - even method' B2 Tooltip: 'Select 2 or more vert loops, then run this script' """ __author__ = "Campbell Barton" __url__ = ["blender", "elysiun"] __version__ = "1.0 2004/04/25" __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, if the results of a method are not adequate 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 * arg = __script__['arg'] #================# # 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): return [ls[-1]] + ls[:-1] #=================================================================# # 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): 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: V1 = selVertBetween2Faces(mesh.faces[v1loop[0]], mesh.faces[v1loop[1]]) V2 = selVertBetween2Faces(mesh.faces[v2loop[0]], mesh.faces[v2loop[1]]) P1 = (V1[0],V1[1],V1[2]) P2 = (V2[0],V2[1],V2[2]) totalDist += measure(P1,P2) v1loop = listRotate(v1loop) v2loop = listRotate(v2loop) #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 = eval(str(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 [0, 0]: dist = measureVloop(mesh, v1loop, v2loop) # Initialize the Best distance recorded if bestSoFar == None: bestSoFar = dist bestv2Loop = eval(str(v2loop)) elif dist < bestSoFar: # Update the info if a better vloop rotation is found. bestSoFar = dist bestv2Loop = eval(str(v2loop)) # We might have got the vert loop backwards, try the other way v2loop.reverse() v2loop = 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 [0, 0]: dist = measureVloop(mesh, v1loop, v2loop, surplusFaces) print 'dist', dist # Initialize the Best distance recorded if bestSoFar == None: bestSoFar = dist bestv2Loop = eval(str(v2loop)) elif dist < bestSoFar: # Update the info if a better vloop rotation is found. bestSoFar = dist bestv2Loop = eval(str(v2loop)) # We might have got the vert loop backwards, try the other way v2loop.reverse() 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 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') if mesh != None: 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() if is_editmode: Window.EditMode(1)