forked from bartvdbraak/blender
4b01aa7aa5
The orange -> HEAD merge reverted some scripts to older versions. This only affected the ones that already existed before the orange branch. Minor issue, easy to fix. All in all, kudos to kaito, Hos and others for all the hard work in bringing (coding, merging) all these changes to the main branch.
492 lines
13 KiB
Python
492 lines
13 KiB
Python
#!BPY
|
|
|
|
"""
|
|
Name: 'Bridge Faces/Edge-Loops'
|
|
Blender: 237
|
|
Group: 'Mesh'
|
|
Tooltip: 'Select 2 vert loops, then run this script.'
|
|
"""
|
|
|
|
__author__ = "Campbell Barton AKA Ideasman"
|
|
__url__ = ["http://members.iinet.net.au/~cpbarton/ideasman/", "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 2005/06/15 - ideasman@linuxmail.org
|
|
|
|
import Blender
|
|
from Blender import *
|
|
|
|
BIG_NUM = 1<<30
|
|
|
|
global CULL_METHOD
|
|
CULL_METHOD = 0
|
|
|
|
class edge:
|
|
def __init__(self, v1,v2):
|
|
self.v1 = v1
|
|
self.v2 = v2
|
|
|
|
# uv1 uv2 vcol1 vcol2 # Add later
|
|
self.length = (v1.co - v2.co).length
|
|
|
|
self.removed = 0 # Have we been culled from the eloop
|
|
self.match = None # The other edge were making a face with
|
|
|
|
|
|
class edgeLoop:
|
|
def __init__(self, loop): # Vert loop
|
|
# Use next and prev, nextDist, prevDist
|
|
|
|
# Get Loops centre.
|
|
self.centre = Mathutils.Vector()
|
|
f = 1.0/len(loop)
|
|
for v in loop:
|
|
self.centre += v.co * f
|
|
|
|
|
|
|
|
|
|
# Convert Vert loop to Edges.
|
|
self.edges = []
|
|
vIdx = 0
|
|
while vIdx < len(loop):
|
|
self.edges.append( edge(loop[vIdx-1], loop[vIdx]) )
|
|
vIdx += 1
|
|
|
|
# Assign linked list
|
|
for eIdx in range(len(self.edges)-1):
|
|
self.edges[eIdx].next = self.edges[eIdx+1]
|
|
self.edges[eIdx].prev = self.edges[eIdx-1]
|
|
# Now last
|
|
self.edges[-1].next = self.edges[0]
|
|
self.edges[-1].prev = self.edges[-2]
|
|
|
|
|
|
|
|
# GENERATE AN AVERAGE NORMAL FOR THE WHOLE LOOP.
|
|
self.normal = Mathutils.Vector()
|
|
for e in self.edges:
|
|
n = Mathutils.CrossVecs(self.centre-e.v1.co, self.centre-e.v2.co)
|
|
# Do we realy need tot normalize?
|
|
n.normalize()
|
|
self.normal += n
|
|
self.normal.normalize()
|
|
|
|
|
|
# Generate a normal for each edge.
|
|
for e in self.edges:
|
|
|
|
n1 = e.v1.co
|
|
n2 = e.v2.co
|
|
n3 = e.prev.v1.co
|
|
|
|
a = n1-n2
|
|
b = n1-n3
|
|
normal1 = Mathutils.CrossVecs(a,b)
|
|
normal1.normalize()
|
|
|
|
n1 = e.v2.co
|
|
n3 = e.next.v2.co
|
|
n2 = e.v1.co
|
|
|
|
a = n1-n2
|
|
b = n1-n3
|
|
|
|
normal2 = Mathutils.CrossVecs(a,b)
|
|
normal2.normalize()
|
|
|
|
# Reuse normal1 var
|
|
normal1 += normal1 + normal2
|
|
normal1.normalize()
|
|
|
|
e.normal = normal1
|
|
#print e.normal
|
|
|
|
|
|
|
|
|
|
def backup(self):
|
|
# Keep a backup of the edges
|
|
self.backupEdges = self.edges[:]
|
|
|
|
def restore(self):
|
|
self.edges = self.backupEdges[:]
|
|
for e in self.edges:
|
|
e.removed = 0
|
|
|
|
def reverse(self):
|
|
self.edges.reverse()
|
|
for e in self.edges:
|
|
e.normal = -e.normal
|
|
e.v1, e.v2 = e.v2, e.v1
|
|
self.normal = -self.normal
|
|
|
|
# Removes N Smallest edges and backs up
|
|
def removeSmallest(self, cullNum, otherLoopLen):
|
|
global CULL_METHOD
|
|
if CULL_METHOD == 0: # Shortest edge
|
|
|
|
eloopCopy = self.edges[:]
|
|
eloopCopy.sort(lambda e1, e2: cmp(e1.length, e2.length )) # Length sort, smallest first
|
|
eloopCopy = eloopCopy[:cullNum]
|
|
for e in eloopCopy:
|
|
e.removed = 1
|
|
self.edges.remove( e ) # Remove from own list, still in linked list.
|
|
|
|
else: # CULL METHOD is even
|
|
|
|
culled = 0
|
|
|
|
step = int(otherLoopLen / float(cullNum))
|
|
|
|
currentEdge = self.edges[0]
|
|
while culled < cullNum:
|
|
|
|
# Get the shortest face in the next STEP
|
|
while currentEdge.removed == 1:
|
|
# Bug here!
|
|
currentEdge = currentEdge.next
|
|
smallestEdge = currentEdge
|
|
|
|
for i in range(step):
|
|
currentEdge = currentEdge.next
|
|
while currentEdge.removed == 1:
|
|
currentEdge = currentEdge.next
|
|
if smallestEdge.length > currentEdge.length:
|
|
smallestEdge = currentEdge
|
|
|
|
# In that stepping length we have the smallest edge.remove it
|
|
smallestEdge.removed = 1
|
|
self.edges.remove(smallestEdge)
|
|
|
|
culled+=1
|
|
|
|
|
|
# Returns face edges.
|
|
# face must have edge data.
|
|
def faceEdges(me, f):
|
|
if len(f) == 3:
|
|
return [\
|
|
me.findEdge(f[0], f[1]),\
|
|
me.findEdge(f[1], f[2]),\
|
|
me.findEdge(f[2], f[0])\
|
|
]
|
|
elif len(f) == 4:
|
|
return [\
|
|
me.findEdge(f[0], f[1]),\
|
|
me.findEdge(f[1], f[2]),\
|
|
me.findEdge(f[2], f[3]),\
|
|
me.findEdge(f[3], f[0])\
|
|
]
|
|
|
|
|
|
def getSelectedEdges(me, ob):
|
|
SEL_FLAG = NMesh.EdgeFlags['SELECT']
|
|
FGON_FLAG = NMesh.EdgeFlags['FGON']
|
|
|
|
edges = [e for e in me.edges if e.flag & SEL_FLAG if (e.flag & FGON_FLAG) == 0 ]
|
|
|
|
# Now remove edges that face 2 or more selected faces usoing them
|
|
edgeFromSelFaces = []
|
|
for f in me.faces:
|
|
if len(f) >2 and f.sel:
|
|
edgeFromSelFaces.extend(faceEdges(me, f))
|
|
|
|
# Remove all edges with 2 or more selected faces as uses.
|
|
for e in edgeFromSelFaces:
|
|
if edgeFromSelFaces.count(e) > 1:
|
|
me.removeEdge(e.v1, e.v2)
|
|
|
|
# Remove selected faces?
|
|
fIdx = len(me.faces)
|
|
while fIdx:
|
|
fIdx-=1
|
|
if len(me.faces[fIdx]) > 2:
|
|
if me.faces[fIdx].sel:
|
|
me.faces.pop(fIdx)
|
|
return [e for e in edges if edgeFromSelFaces.count(e) < 2]
|
|
|
|
|
|
# Like vert loops
|
|
def getVertLoops(selEdges):
|
|
mainVertLoops = []
|
|
while selEdges:
|
|
e = selEdges.pop()
|
|
contextVertLoop= [e.v1, e.v2] # start the vert loop
|
|
|
|
eIdx = 1 # Get us into the loop. dummy var.
|
|
|
|
# if eIdx == 0 then it means we searched and found no matches...
|
|
# time for a new vert loop,
|
|
while eIdx:
|
|
eIdx = len(selEdges)
|
|
while eIdx:
|
|
eIdx-=1
|
|
|
|
# Check for edge attached at the head of the loop.
|
|
if contextVertLoop[0] == selEdges[eIdx].v1:
|
|
contextVertLoop.insert(0, selEdges.pop(eIdx).v2)
|
|
elif contextVertLoop[0] == selEdges[eIdx].v2:
|
|
contextVertLoop.insert(0, selEdges.pop(eIdx).v1)
|
|
|
|
# Chech for edge vert at the tail.
|
|
elif contextVertLoop[-1] == selEdges[eIdx].v1:
|
|
contextVertLoop.append(selEdges.pop(eIdx).v2)
|
|
elif contextVertLoop[-1] == selEdges[eIdx].v2:
|
|
contextVertLoop.append(selEdges.pop(eIdx).v1)
|
|
else:
|
|
# None found? Keep looking
|
|
continue
|
|
|
|
# Once found we.
|
|
break
|
|
|
|
# Is this a loop? if so then its forst and last vert must be teh same.
|
|
if contextVertLoop[0].index == contextVertLoop[-1].index:
|
|
contextVertLoop.pop() # remove double vert
|
|
mainVertLoops.append(contextVertLoop)
|
|
|
|
# Build context vert loops
|
|
return mainVertLoops
|
|
|
|
|
|
def skin2EdgeLoops(eloop1, eloop2, me, ob, MODE):
|
|
# Make sure e1 loops is bigger then e2
|
|
if len(eloop1.edges) != len(eloop2.edges):
|
|
if len(eloop1.edges) < len(eloop2.edges):
|
|
eloop1, eloop2 = eloop2, eloop1
|
|
|
|
eloop1.backup() # were about to cull faces
|
|
CULL_FACES = len(eloop1.edges) - len(eloop2.edges)
|
|
eloop1.removeSmallest(CULL_FACES, len(eloop1.edges))
|
|
else:
|
|
CULL_FACES = 0
|
|
# First make sure poly vert loops are in sync with eachother.
|
|
|
|
# The vector allong which we are skinning.
|
|
skinVector = eloop1.centre - eloop2.centre
|
|
|
|
loopDist = skinVector.length
|
|
|
|
|
|
# IS THE LOOP FLIPPED, IF SO FLIP BACK.
|
|
angleBetweenLoopNormals = Mathutils.AngleBetweenVecs(eloop1.normal, eloop2.normal)
|
|
|
|
if angleBetweenLoopNormals > 90:
|
|
eloop2.reverse()
|
|
|
|
|
|
bestEloopDist = BIG_NUM
|
|
bestOffset = 0
|
|
# Loop rotation offset to test.1
|
|
eLoopIdxs = range(len(eloop1.edges))
|
|
for offset in range(len(eloop1.edges)):
|
|
totEloopDist = 0 # Measure this total distance for thsi loop.
|
|
|
|
offsetIndexLs = eLoopIdxs[offset:] + eLoopIdxs[:offset] # Make offset index list
|
|
|
|
# e1Idx is always from 0 to N, e2Idx is offset.
|
|
for e1Idx, e2Idx in enumerate(offsetIndexLs):
|
|
# Measure the vloop distance ===============
|
|
totEloopDist += ((eloop1.edges[e1Idx].v1.co - eloop2.edges[e2Idx].v1.co).length / loopDist) #/ nangle1
|
|
totEloopDist += ((eloop1.edges[e1Idx].v2.co - eloop2.edges[e2Idx].v2.co).length / loopDist) #/ nangle1
|
|
|
|
# Premeture break if where no better off
|
|
if totEloopDist > bestEloopDist:
|
|
break
|
|
|
|
if totEloopDist < bestEloopDist:
|
|
bestOffset = offset
|
|
bestEloopDist = totEloopDist
|
|
|
|
# Modify V2 LS for Best offset
|
|
eloop2.edges = eloop2.edges[bestOffset:] + eloop2.edges[:bestOffset]
|
|
|
|
|
|
|
|
for loopIdx in range(len(eloop2.edges)):
|
|
e1 = eloop1.edges[loopIdx]
|
|
e2 = eloop2.edges[loopIdx]
|
|
|
|
# Remember the pairs for fan filling culled edges.
|
|
e1.match = e2; e2.match = e1
|
|
|
|
# need some smart face flipping code here.
|
|
f = NMesh.Face([e1.v1, e1.v2, e2.v2, e2.v1])
|
|
|
|
f.sel = 1
|
|
me.faces.append(f)
|
|
|
|
# FAN FILL MISSING FACES.
|
|
if CULL_FACES:
|
|
# Culled edges will be in eloop1.
|
|
FAN_FILLED_FACES = 0
|
|
|
|
contextEdge = eloop1.edges[0] # The larger of teh 2
|
|
while FAN_FILLED_FACES < CULL_FACES:
|
|
while contextEdge.next.removed == 0:
|
|
contextEdge = contextEdge.next
|
|
|
|
vertFanPivot = contextEdge.match.v2
|
|
|
|
while contextEdge.next.removed == 1:
|
|
|
|
f = NMesh.Face([contextEdge.next.v1, contextEdge.next.v2, vertFanPivot] )
|
|
|
|
|
|
f.sel = 1
|
|
me.faces.append(f)
|
|
|
|
# Should we use another var?, this will work for now.
|
|
contextEdge.next.removed = 1
|
|
|
|
contextEdge = contextEdge.next
|
|
FAN_FILLED_FACES += 1
|
|
|
|
eloop1.restore() # Add culled back into the list.
|
|
#if angleBetweenLoopNormals > 90:
|
|
# eloop2.reverse()
|
|
|
|
|
|
def main():
|
|
global CULL_METHOD
|
|
|
|
is_editmode = Window.EditMode()
|
|
if is_editmode: Window.EditMode(0)
|
|
ob = Scene.GetCurrent().getActiveObject()
|
|
if ob == None or ob.getType() != 'Mesh':
|
|
return
|
|
|
|
me = ob.getData()
|
|
if not me.edges:
|
|
Draw.PupMenu('Error, add edge data first')
|
|
if is_editmode: Window.EditMode(1)
|
|
return
|
|
|
|
# BAD BLENDER PYTHON API, NEED TO ENTER EXIT EDIT MODE FOR ADDING EDGE DATA.
|
|
# ADD EDGE DATA HERE, Python API CANT DO IT YET, LOOSES SELECTION
|
|
|
|
selEdges = getSelectedEdges(me, ob)
|
|
vertLoops = getVertLoops(selEdges) # list of lists of edges.
|
|
|
|
if len(vertLoops) > 2:
|
|
choice = Draw.PupMenu('Loft '+str(len(vertLoops))+' edge loops%t|loop|segment')
|
|
if choice == -1:
|
|
if is_editmode: Window.EditMode(1)
|
|
return
|
|
elif len(vertLoops) < 2:
|
|
Draw.PupMenu('Error, No Vertloops found%t|if you have a valid selection, go in and out of face edit mode to update the selection state.')
|
|
if is_editmode: Window.EditMode(1)
|
|
return
|
|
else:
|
|
choice = 2
|
|
|
|
|
|
# The line below checks if any of the vert loops are differenyt in length.
|
|
if False in [len(v) == len(vertLoops[0]) for v in vertLoops]:
|
|
CULL_METHOD = Draw.PupMenu('Small to large edge loop distrobution method%t|remove edges evenly|remove smallest edges edges')
|
|
if CULL_METHOD == -1:
|
|
if is_editmode: Window.EditMode(1)
|
|
return
|
|
|
|
if CULL_METHOD ==1: # RESET CULL_METHOD
|
|
CULL_METHOD = 0 # shortest
|
|
else:
|
|
CULL_METHOD = 1 # even
|
|
|
|
|
|
time1 = sys.time()
|
|
# Convert to special edge data.
|
|
edgeLoops = []
|
|
for vloop in vertLoops:
|
|
edgeLoops.append(edgeLoop(vloop))
|
|
|
|
|
|
# VERT LOOP ORDERING CODE
|
|
# Build a worm list - grow from Both ends
|
|
edgeOrderedList = [edgeLoops.pop()]
|
|
|
|
# Find the closest.
|
|
bestSoFar = BIG_NUM
|
|
bestIdxSoFar = None
|
|
for edLoopIdx, edLoop in enumerate(edgeLoops):
|
|
l =(edgeOrderedList[-1].centre - edLoop.centre).length
|
|
if l < bestSoFar:
|
|
bestIdxSoFar = edLoopIdx
|
|
bestSoFar = l
|
|
|
|
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
|
|
|
|
# Now we have the 2 closest, append to either end-
|
|
# Find the closest.
|
|
while edgeLoops:
|
|
bestSoFar = BIG_NUM
|
|
bestIdxSoFar = None
|
|
first_or_last = 0 # Zero is first
|
|
for edLoopIdx, edLoop in enumerate(edgeLoops):
|
|
l1 =(edgeOrderedList[-1].centre - edLoop.centre).length
|
|
|
|
if l1 < bestSoFar:
|
|
bestIdxSoFar = edLoopIdx
|
|
bestSoFar = l1
|
|
first_or_last = 1 # last
|
|
|
|
l2 =(edgeOrderedList[0].centre - edLoop.centre).length
|
|
if l2 < bestSoFar:
|
|
bestIdxSoFar = edLoopIdx
|
|
bestSoFar = l2
|
|
first_or_last = 0 # last
|
|
|
|
if first_or_last: # add closest Last
|
|
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
|
|
else: # Add closest First
|
|
edgeOrderedList.insert(0, edgeLoops.pop(bestIdxSoFar) ) # First
|
|
|
|
for i in range(len(edgeOrderedList)-1):
|
|
skin2EdgeLoops(edgeOrderedList[i], edgeOrderedList[i+1], me, ob, 0)
|
|
if choice == 1 and len(edgeOrderedList) > 2: # Loop
|
|
skin2EdgeLoops(edgeOrderedList[0], edgeOrderedList[-1], me, ob, 0)
|
|
|
|
print '\nArray done in %.4f sec.' % (sys.time()-time1)
|
|
me.update(1, 1, 0)
|
|
if is_editmode: Window.EditMode(1)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|