#!BPY
"""
Name: 'MegaBool'
Blender: 234
Group: 'Object'
Tooltip: 'Perform boolean operations on meshes and get nice results'
"""
__bpydoc__ ="""\
Experimental New Mesh Boolean Operations
(C) 2004 Theodore K Schundler (tschundler)(@)(scu)(edu)
FOR TESTING ONLY, not for redistribution
Lastest version available for from:
http://astro.scu.edu/~ted/oss/blender/boolean
USAGE:
-By Menu-
* Put this file in ~/.blender/scripts/
* Select two objects, then choose "MegaBool" from the
scripts menu.
-By Running the script-
* Load this file into blender's internal text editor
* Select two objects, then press Alt+P in the test editor
OPERATIONS:
On execution of the script, there are four operations
* Intersect - Intersection of the two meshes (logical AND)
* Union - Union of the two meshes (logical OR)
* Difference - First selected mesh - Last selected mesh
* Cookie Cutter - First mesh's faces are subdivided where
they intersect with the second mesh
After the script executes, all "inside" vertices are selected
IMPORTANT NOTES:
Like any boolean algorithm, normals on the source meshes must
be all facing outside. To fix normals, select a mesh, enter
edit mode, select all verticies, and press Ctrl+N. Also, all
intersections must create closed loops. (Basically the meshes
should be closed.) If you find a set of meshes that do not work
send me the .blend file and an explaination of the problem.
TO DO:
* Set materials, UVs, etc., on new mesh
* Intersection of curves? (use splines for smooth shapes)
-----------------------------------------------------------------
Date: Updates:
16-Aug-2004 Initial Revision - working(?) & ready to go
17-Aug-2004 Fixed some bugs based on incorrect assumptions.
that means even more computation. Also made more
use of the traversalCookies to speed up a some
sections
24-Aug-2004 Smart filling & other stuff...
26-Aug-2004 Much cleanup, nothing new / fancy
18-Sep-2004 Re-did filling. 2nd object can now be a set of
loops. Other minor refinements.
24-Sep-2004 Fixed a wrong assumption about vertex ordering
26-Sep-2004 Got rid of special filling stuff that made face
selection work better because with the new edit
modes, it isn't necissary.
27-Oct-2004 Made some headway with the coplanar problem, but
there is still a colinear problem, and sometimes
an issue if a point lies in another objects plane
01-Nov-2004 Fixed edge crossing tests in joinconcentric
10-Dec-2004 Rewrite of almost everything
"""
__author__ = "Theodore K. Schundler"
__version__ = "r8 10-Dec-04"
__email__ = ('Author EMail, tschundler:scu*edu')
__url__ = ('elysiun','Script Web Page, http://astro.scu.edu/~ted/oss/blender/boolean/')
import Blender
from Blender import NMesh
from Blender import Object
from Blender import Draw
from Blender import Mathutils
from Blender.Mathutils import *
traversalCookie=0
messages=""
###################################################################
# Utility Functions #
###################################################################
def MidPoint(v1,v2):
return (v1*0.49+v2*0.51); #a little bit of jitter ...
def ExpandBounds(Bounds,v):
for i in range(3):
if (Bounds[0][i]>v[i]):
Bounds[0][i]=v[i]
if (Bounds[1][i]b2[1][i]) or (b2[0][i]>b1[1][i]):
return 0
return 1
def FindIntersectLineLine3D(p1,p2,p3,p4,stop_at_a=0,limits=0,advanced=0,tolerance=0.0002):
if limits:
#First test for shared points
if (p1==p3 or p1==p4 or p2==p3 or p2==p4):
return []
#Bounds test...maybe it should be tested to see if this actually helps?
if (TestBounds(BuildBounds(p1,p2),BuildBounds(p3,p4))==0):
return []
#based on http://astronomy.swin.edu.au/~pbourke/geometry/lineline3d/
d2121=DotVecs(p2-p1,p2-p1) #note: square of the edge's length (maybe should be precomputed for all edges?)
d4321=DotVecs(p4-p3,p2-p1)
d4343=DotVecs(p4-p3,p4-p3) #note: square of edge's length again
d1343=DotVecs(p1-p3,p4-p3)
d1321=DotVecs(p1-p3,p2-p1)
mnad = (d2121*d4343-d4321*d4321)
if mnad==0:#mnad>-0.00000000000001 and mnad<0.00000000000001: #parallel
#print "Parallel!!!",mnad
return []
else:
mna = (d1343*d4321-d1321*d4343)/mnad
if (limits==0 or (mna>-0.000001 and mna <1.000001)):
mnb = (d1343+mna*d4321)/d4343
if ((limits==0 and stop_at_a==0) or (mnb > -0.00001 and mnb <1.000001)):
if mnad<0.001 or advanced:
dv=p1+(p2-p1)*mna-(p3+(p4-p3)*mnb)
dis=DotVecs(dv,dv)
#Needs to be 0.00001 for animtest to work...
#Maybe is I break Quads into tris, this won't be a problem?
if dis>tolerance:
#print "Parallel - late detection",dv,dis,(p3+(p4-p3)*mnb)
return []
return [mna,mnb,dis]
return [mna,mnb]
return []
def FindDistPointToLine3D(p1,p2,p3):
Va=p2-p1
Vb=p3-p1
d=DotVecs(Va,Va)
if d!=0:
p4= ((DotVecs(Vb,Va)/d)*Va)+p1
else:
p4=p1
Vc=p4-p3
return DotVecs(Vc,Vc)
def TestVertInEdge3D(v,e):
d=e.dot
if d!=0:
vA=v.tco-e.v[0].tco
dA=DotVecs(vA,e.vect)
de=dA/d
if (de>1.0001 or de<-0.0001):
return 0
dp=de*d+e.v[0].tco
else:
dp=e.v[0].tco
vB=dp-v.tco
dB=DotVecs(vB,vB)
if dB<0.0001:
return 1
return 0
def TestIntersectLineLine3D(p1,p2,p3,p4):
return len(FindIntersectLineLine3D(p1,p2,p3,p4,0,1))==2
def TestEdgeIntersectLoop3D(p0,p1,loop):
lastv=loop[len(loop)-1]
for v in loop:
if TestIntersectLineLine3D(p0.tco,p1.tco,lastv.tco,v.tco):
return 1
lastv=v
return 0
def TestPointSideOfLine3D(point,v0,v1,normal):
#Note: Maybe I should store the edge's normal on the edge?
#This function gets called from different loops, so it may be worth it
cp=CrossVecs(normal,v1-v0)
s=DotVecs(point-v0,cp)
if s < 0:
return 0
else:
return 1
#test if a point is within the edges of a convex face
#Basically we make sure the point is on the "inside" side
#of all the edges of the shape.
#it may be worthwhile to optimize this since uit gets a called a lot when filling
def TestPointInConvexLoop(p,l,normal):
lastv=l[len(l)-1]
for v in l:
if not TestPointSideOfLine3D(p,lastv.tco,v.tco,normal):
return 0
lastv=v
return 1
def FindIntersectRayPlane(r_point,r_direction,p_point,p_normal,positive_only=0):
d=DotVecs(p_normal,r_direction)
if abs(d)<0.001:
return 0
n=DotVecs(p_normal,p_point-r_point)
nd=n/d
if nd<0 and positive_only:
return 0
return nd
def TestPointInPlane(point,normal,facevert):
va=point-facevert
#normal's length should always be 1....why isn't it?
d=DotVecs(normal,va)/normal.length
#must be <0.001 ....apparently much less than 0.001 is a problem too
#>=0.001 needed for animtest frame 82
if d<0.001 and d > -0.001:
print "Point in Plane:",d,point,normal,facevert
return 1
return 0
def TestPointInFace(p,f):
vcount=len(f.v)
if vcount:
return TestPointInConvexLoop(p,f.v,f.normal)
else: #This is an infinitely large face, so we test between the two edges
v0=f.edges[0].v[0]
v1=f.edges[1].v[0]
d0=DotVecs(v1.tco-v0.tco,p-v0.tco)
d1=DotVecs(v0.tco-v1.tco,p-v1.tco)
if d0>0 and d1>0:
#print "PIF",d0,d1,v0.tco,v1.tco,p
return 1
else:
#print "PNIF"
return 0
def TestIntersectRayFace(r_point,r_direction,face,positive_only=0):
d=FindIntersectRayPlane(r_point,r_direction,face.e[0].v[0].tco,face.normal,positive_only)
if d==0:
return 0
p=r_point+d*r_direction
return TestPointInFace(p,face)
def FindIntersectEdgeFace(edge,face):
r_point=edge.v[0].tco
r_direction=edge.v[1].tco-r_point
d=FindIntersectRayPlane(r_point,r_direction,face.e[0].v[0].tco,face.normal)
if d==0:
return 0
#May need to fudge these valus for fuzzy intersections
if (not edge.infinite) and (d<0 or d>1):
return 0
p=r_point+d*r_direction
if TestPointInFace(p,face):
return p
return 0
def MakeLoopX(start,vA,state,face,lastEdge=0):
global traversalCookie
#print "LS",vA,vA.e
for e in vA.e:
if e.travCookie3:
if nFillLoop(4,vertList,CCWlist,normal,oldFace,newMesh,selected,0.00001):
return 1
#Three points or quad fill failed, we try to fill with tris
if len(vertList)>2:
if nFillLoop(3,vertList,CCWlist,normal,oldFace,newMesh,selected,0.00001):
return 1
else:
#if it failed, don't be as picky about what is convex.
if len(vertList)>3:
if nFillLoop(4,vertList,CCWlist,normal,oldFace,newMesh,selected,0):
return 1
#Three points or quad fill failed, we try to fill with tris
if nFillLoop(3,vertList,CCWlist,normal,oldFace,newMesh,selected,0):
return 1
else:
return 0
#if there are less than three points we're done
else:
return 1
def FillPreprocess(VertexLists,normal):
RightWay=[]
WrongWay=[]
for ThisList in VertexLists:
lastTest=ThisList[len(ThisList)-1]
FlipList=0
Direction=0
#this runs the test multiple times. Realy just two times were you get the same result in
#a row should be sufficient, or once in "fast mode"
for testpt in ThisList:
#Have the ray start midway on the segment because it may get confused on the edges
midpt=MidPoint(lastTest.tco,testpt.tco)
#The ray is perpendicular to the edge and the normal
ray=CrossVecs(testpt.tco-lastTest.tco,normal)
#print "B:",ThisList[0].tco,ThisList[1].tco,"->",midpt,ray
isect_counter_all=0
isect_counter_others=0
for OtherList in VertexLists:
prevVert=OtherList[len(OtherList)-1]
for Vert in OtherList:
#print Vert.tco
if not ((prevVert==lastTest)): #Don't even try to test for intersection with self
#print "TX: ",midpt,midpt+ray,prevVert.tco,Vert.tco,testpt.tco,lastTest.tco
isect=FindIntersectLineLine3D(midpt,midpt+ray,prevVert.tco,Vert.tco,1,0,0)
#if len(isect):
# print "\tX:",isect[0],isect[1],midpt+ray*isect[0],isect[2]
if (len(isect)>0 and isect[0]>0):
isect_counter_all+=1
if (not OtherList==ThisList):
isect_counter_others+=1
prevVert=Vert
#if the ray intersects an odd number of edges, it is pointing in the wrong direction
#if it is pointing in the wrong direction, the verticies aren't ordered right
if (isect_counter_all&1==1):
#print "Flipped List"
FlipList+=1
#ThisList.reverse()
else:
FlipList-=1
#if it intersects an even number of edges of objects other than it self
#then it is an outer loop in a set of concentric loops (i.e. the outer edge of a doughnut)
if (isect_counter_others&1==0):
Direction+=1
#RightWay.append(ThisList);
#otherwise it is an inner loop (the edges that make up the hole of a doughnut)
#and the verts are ordered in the other direction
else:
Direction-=1
#WrongWay.append(ThisList);
lastTest=testpt
#apply direction...
#print "Flipped: ",FlipList,"Direction:",Direction
if FlipList>0:
ThisList.reverse()
if Direction>0:
RightWay.append(ThisList)
else:
WrongWay.append(ThisList)
lastTest=testpt
#print "Right Way:",len(RightWay),"WrongWay:",len(WrongWay)
return [RightWay,WrongWay]
def FillLoops(loops,oldFace,newMesh,flip,selected):
if loops==[]:
return
#print "SRART FILLL------"
#if the face needs to be flipped (i.e. when doing a difference operation)
normal=oldFace.normal*flip
#First establish loop directions
FixedLists=FillPreprocess(loops,normal)
#Now make two lists - clockwise and counterclockwise
CWlist=FixedLists[0]
CCWlist=FixedLists[1]
#Time to fill!
for l in CWlist:
i=FillProcessedLoop(l,CCWlist,normal,oldFace,newMesh,selected)
if i==0:
print "\t\t\tFill Failed..."
for v in l:
print "\t\t\t\t",v.tco,v
###################################################################
# Boolean Op objects #
###################################################################
# 0 -> Unknown
# 1 -> Outside
# 2 -> Inside
# 3 -> Coplanar -> normals in same direction
# 4 -> Coplanar -> opposing normals
# 5 -> cut face
class bVert:
def __init__(self,coord,normal=0):
global traversalCookie
self.travCookie=traversalCookie
coord.resize3D()
self.tco=coord
self.sharedvert=0
if normal:
self.normal=normal
else:
self.normal=Vector([0.0,0.0,0.0,1.0])
self.e=[]
self.f=[]
self.newCounterpart=0
self.redirect_to=0
self.redirect_from=0
def findSide(self,otherObject):
print "\t\t\tlame vert side look up -",self,self.tco
ray=Vector([1,0,0])
ifaces=0
for f in otherObject.faces:
if TestIntersectRayFace(self.tco,ray,f,1):
ifaces+=1
if (ifaces%2)==1:
return 2
else:
return 1
def ApplyFaceIntersection(v,f):
print "VERT LIES IN FACE! (",v," in ",f,")--------------------------------------------------------------------------------"
for ve in v.e:
if ve.v[0].GetRedirectTo()==v.GetRedirectTo():
evect=ve.vect
else:
evect=ve.vect*(-1)
d=DotVecs(evect,f.normal)
if d>0.000001:
ve.state=1
for ef in ve.f:
ef[0]=1
elif d<-0.000001:
ve.state=2
for ef in ve.f:
ef[0]=2
else:
print "EDGE LIES IN FACE!!!! ",d
ve.ApplyIntersection(f,v)
#mark as shared and such and move on
v.sharedvert=1
v.f.append(f)
f.extraVerts.append(v)
f.state=5
def GetRedirectTo(vA):
if vA.redirect_to==0:
return vA
return vA.redirect_to.GetRedirectTo()
def GetRedirectFrom(vA):
if vA.redirect_from==0:
return vA
return vA.redirect_from.GetRedirectFrom()
def MergeVert(vA,vB):
if vA.GetRedirectTo()==vB.GetRedirectTo():
return
dest=vA.GetRedirectFrom()
src=vB.GetRedirectTo()
src.redirect_to=dest
dest.redirect_from=src
print "Redirection Created!!!!",src.tco,dest.tco
def real(self):
if (self.redirect_to!=0):
return self.GetRedirectTo().real()
if (self.redirect_from==0):
return self
nextfrom=self.redirect_from
while (nextfrom):
#if nextfrom.crossed:
# self.crossed=1
if nextfrom.sharedvert:
self.sharedvert=1
for nff in nextfrom.f:
notfound=1
for f in self.f:
if (f==nff):
notfound=0
if (notfound):
#print "af:",nff
self.f.append(nff)
for e in nextfrom.e:
if e.v[0]==nextfrom:
e.v[0]=self
elif e.v[1]==nextfrom:
e.v[1]=self
self.e.append(e)
nextfrom.redirect_to=self
nextfrom=nextfrom.redirect_from
self.redirect_from=0
print "RVert: ",self,self.tco#,self.inside,self.outside,self.cutBy
return self
class bEdge:
def __init__(self,bv0,bv1,infinite=0):
global traversalCookie
self.travCookie=traversalCookie
i=0
self.children=[]
self.cutpoint=0
self.f=[]
self.v=[bv0,bv1]
self.bb=BuildBounds(bv0.tco,bv1.tco)
self.vect=bv1.tco-bv0.tco
self.len=self.vect.length
self.dot=DotVecs(self.vect,self.vect)
self.infinite=0
self.state=0
bv0.e.append(self)
bv1.e.append(self)
print "\t\tAdding Edge... ",self,bv0.tco,bv1.tco
def ApplyIntersection(e,f,v):
#Step 1 - remove instances of this face:
for ef in e.f:
if ef[1]==f:
e.f.remove(ef)
op=Vector([0,0,0])
cofaces=0
#Step 2 - figure out the two original face's sides
for ef in e.f:
if ef[1].parent==e.f[0][1].parent:
dc=DotVecs(f.normal,ef[1].center-v.tco)
if abs(dc)<0.000001:
if DotVecs(f.normal,ef[1].normal)>0:
print "\tCOPLANAR FACES - SAME - ",ef,dc
ef[0]=3
e.f.append([3,f])
f.coedges.append(e)
cofaces=1
else:
print "\tCOPLANAR FACES - OPPOSING - ",ef,dc
ef[0]=4
e.f.append([4,f])
f.coedges.append(e)
cofaces=1
elif dc>0: #outside
print "\tOUTSIDE - ",ef,dc
op+=ef[1].center
ef[0]=1
else:
print "\tINSIDE - ",ef,dc
ef[0]=2
op+=ef[1].center
#Step 3 - figure out if this face is anything relative to the edge's faces
if (cofaces==0):
#simple case - part is inside, the other is outside
e.f.append([1,f])
e.f.append([2,f])
else:
nt=e.f[0][1].normal+e.f[1][1].normal
nt.normalize()
dA=DotVecs(nt,f.normal)
dB=DotVecs(op-v.tco,f.normal)
if dA>0.9999999: #cosame
print "\tSELF SAME - ",f,dA,dB,e.f
e.f.append([3,f])
elif dA<-0.9999999:
print "\tSELF OPPOSING - ",f,dA,dB,e.f
e.f.append([4,f])
elif (dA>0 and dB<0) or (dA<0 and dB>0): #outside
print "\tSELF OUTSIDE - ",f,dA,dB,e.f
e.f.append([1,f])
else:
print "\tSELF INSIDE - ",f,dA,dB,e.f
e.f.append([2,f])
def partition(self,cutVert,normals):
#cutpoint=DotVecs(self.vect,cutVert.tco-self.v[0].tco)
cv=cutVert.tco-self.v[0].tco
cutpoint=(DotVecs(cv,self.vect)/self.dot)*self.len
#print "CUTPOINT: ",cutpoint,self.len-cutpoint
#cv2=cutVert.tco-self.v[1].tco
#cutpoint=DotVecs(cv,cv)
print "\t\t Partitioning edge",self," by ",cutVert.tco," between ",self.v[0].tco,self.v[1].tco
#if applicable, perform operation on one of the child segments
if self.children:
cd=cutpoint-self.cutPoint
#0.00001 or smaller needed for booltest-anim? - refined
#maybe 0.001 or so is good... for now using 0.0001
if cd<0.001 and cd >-0.001:
print "\t\tBranching both ways on ",self,"A"
#If applicable...follow branch both ways - needed sometimes
self.children[1].partition(cutVert,normals)
print "\t\tBranching other way on ",self,"B"
self.children[0].partition(cutVert,normals)
return
elif cutpoint>self.cutPoint:
return self.children[1].partition(cutVert,normals)
else:
return self.children[0].partition(cutVert,normals)
#if cutpoint is too close to one of our verts, mergeverts
if cutpoint <0.0004:
self.v[0].MergeVert(cutVert)
A=0
B=self
Adone=1
Bdone=0
elif cutpoint>(self.len-0.0004):
self.v[1].MergeVert(cutVert)
A=self
B=0
Adone=0
Bdone=1
else:
#otherwise, make new edges
self.v[0].e.remove(self)
self.v[1].e.remove(self)
self.cutPoint=cutpoint
A=bEdge(self.v[0],cutVert)
B=bEdge(cutVert,self.v[1])
self.children.append(A)
self.children.append(B)
for f in self.f:
B.f.append([0,f[1]])
A.f.append([0,f[1]])
Adone=0
Bdone=0
Astate=Bstate=0
#determine inside vs outside
normTot=Vector([0.0,0.0,0.0])
ld=0
print "\t\t Normals:",normals
for n in normals:
normTot+=n[0]
vx=n[1]-cutVert.tco
ds=DotVecs(vx,self.vect)
d=DotVecs(n[0],self.vect)
if d<0.000001 and d>-0.000001:
if ds>0:
ae=B
Bdone=1
else:
ae=A
Adone=1
print "\t\t\tEdge cut by parallel face...",cutVert.tco,ae
if ae:
for f in ae.f:
if f[1].parent==ae.f[0][1].parent:
nA=n[0]
#print "f:",f,"n:",n
nB=f[1].normal
d=DotVecs(nA,nB)
if d>0.99999:
print "\t\t\t COPLANAR - SAME",f[0],f[1],d
f[0]=3
ae.ApplyIntersection(n[2],cutVert)
elif d<-0.99999:
print "\t\t\t COPLANAR - OPPOSING",f[0],f[1],d
f[0]=4
else:
d=DotVecs(n[0],f[1].center-cutVert.tco)
print "\t\t\t EDGE-FACE PARALLEL WAS ",f[0],f[1],d
if d>0:
f[0]=1
else:
f[0]=2
print "\t\t\t EDGE-FACE PARALLEL",f[0],f[1],d
else:
if ds>0.00001:
if d>0:
Bstate=1
else:
Bstate=2
elif ds <-0.00001:
if d<0:
Astate=1
else:
Astate=2
print "\t\t\t\tFL:",ds,d
d=DotVecs(normTot,self.vect)
if (d==0): #parallel??
print "Edge cut by a parallel face - something is wrong"
return
elif (d > 0):
if Astate==0:
Astate=2
if Bstate==0:
Bstate=1
else:
if Astate==0:
Astate=1
if Bstate==0:
Bstate=2
#print d,normTot,self.vect,normals
if A and Adone==0:
A.state=Astate
print "\t\t\tA:",Astate,A.v[0].tco,A.v[1].tco,A.f
for f in A.f:
if f[0]>0 and f[0]!=Astate:
print "#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$#"
if f[1].parent==A.f[0][1].parent:
f[0]=Astate
if B and Bdone==0:
B.state=Bstate
print "\t\t\tB:",Bstate,B.v[0].tco,B.v[1].tco,B.f
for f in B.f:
if f[0]>0 and f[0]!=Bstate:
print "#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$#"
if f[1].parent==B.f[0][1].parent:
f[0]=Bstate
if A!=self and B!=self:
self.state=5
#WARNING: Must increase Traversal Cookie before calling this
def getState(self,otherObject,dontguess=0):
global traversalCookie
if self.state:
return self.state
print "Need to resolve state for edge ",self,self.v[0].tco,self.v[1].tco
ts=0
for fc in self.f:
if ts>0 and ts!=fc[0]:
self.state=5
return 5
else:
ts=fc[0]
if ts>0:
self.state=ts
return ts
self.travCookie=traversalCookie
for v in self.v:
rv=v#v.real()
if rv.sharedvert==0:
for e in rv.e:
#if e.f[0][1].parent==self.f[0][1].parent and e.travCookie0 and es<5:
print "\t",self,"using state from ",e,":",es
self.state=es
for f in self.f:
f[0]=es
return es
else:
print "\t",self,"not using state from ",e,":",es,e.f
if dontguess==0:
#all edge failed...gotta do it the lame way with a verts
es=self.v[0].findSide(otherObject)
self.state=es
for f in self.f:
f[0]=es
return es
else:
return 0
def testState(self,state,face):
#if self.state>0 and self.state<5:
# if self.state==state:
# for f in self.f:
# if f[1]==face:
# return 1
# return 0
for f in self.f:
#print "\tState: ",self,f[0],self.state,f[1],face
if f[0]==state and f[1]==face:
return 1
return 0
class bFace:
def __init__(self,nmFace,parent,virtual=0):
global traversalCookie
self.travCookie=traversalCookie
self.parent=parent
self.state=0
self.e=[]
self.v=[]
self.extraVerts=[]
self.coedges=[]
self.center=Vector([0.0,0.0,0.0])
#self.altered=0
print "\tAdding Face..."
self.counterPart=nmFace
if virtual:
self.normal=0
else:
if parent.matrix:
self.normal=VecMultMat(Vector(nmFace.no),parent.rot)
else:
self.normal=Vector(nmFace.no)
#First the verts
for v in nmFace.v:
bv=parent.verts[v.index]
bv.f.append(self) #maybe not needed...
self.v.append(bv)
self.center+=bv.tco
self.center/=len(self.v)
self.bb=BuildBounds(self.v[0].tco,self.v[1].tco)
self.bb=ExpandBounds(self.bb,self.v[2].tco)
self.e.append(MakeEdge(self.v[0],self.v[1],self))
self.e.append(MakeEdge(self.v[1],self.v[2],self))
if len(self.v)==3:
self.e.append(MakeEdge(self.v[2],self.v[0],self))
else:
self.bb=ExpandBounds(self.bb,self.v[3].tco)
self.e.append(MakeEdge(self.v[2],self.v[3],self))
self.e.append(MakeEdge(self.v[3],self.v[0],self))
def getState(self,otherObject):
#figure out inside/outside/etc based on edge if needed
if self.state==0:
global traversalCookie
traversalCookie=traversalCookie+1
self.state=self.e[0].getState(otherObject)
return self.state
def BuildEdgeLoop(self,e1=0):
global traversalCookie
if e1==0:
e1=self.e[0]
while (e1.children):
e1=e1.children[0]
e1.travCookie=traversalCookie
return [e1]+self.BuildEdgeLoop(e1)
for v in e1.v:
for e in v.e:
if e.travCookie-0.999999 and d <0.999999:
v.fl.append(f)
# else:
# for e in v.e:
# for ef in e.f:
# if ef[1]==self:
# allEdges.append(e)
#Build new edges
c=len(allVerts)
for i in range(c):
vA=allVerts[i]
for j in range(i+1,c):
vB=allVerts[j]
for fA in vA.fl:
for fB in vB.fl:
if fA==fB:
print "\t Face Cut...",vA.tco,vB.tco,fA,self,fA.parent,self.parent
e=MakeEdge(vA,vB,self,1)
if e!=[]:
e.state=5
allEdges.append(e)
break
#for e in allEdges:
# print "ED:",e.getState(),e.v[0].tco,e.v[1].tco
#Build & fill loops
ol=[]
il=[]
cs=[]
co=[]
ol=FilterEdges(allEdges,1,self)
il=FilterEdges(allEdges,2,self)
cs=FilterEdges(allEdges,3,self)
co=FilterEdges(allEdges,4,self)
print "All Verts in ",self
for v in allVerts:
print "\t",v,v.tco
print "All Edges in ",self
for e in allEdges:
print "\t",e,e.v[0].tco,e.v[1].tco,e.v
for f in e.f:
if f[1]==self:
print "\t\t->",f
else:
print "\t\t ",f
print "Filtered loops: for ",self
print "\tOL: ",ol
print "\tIL: ",il
print "\tCS: ",cs
print "\tCO: ",co
if outside:
FillLoops(ol,self,destMesh,flip,0)
if inside:
FillLoops(il,self,destMesh,flip,0)
if cosame:
FillLoops(cs,self,destMesh,flip,0)
if coopposing:
FillLoops(co,self,destMesh,flip,0)
class bMesh:
def __init__(self,blenderObject,matrix):
print "Adding mesh from: %s"%blenderObject.getName()
#setup variables
self.verts=[]
self.faces=[]
self.edges=[]
self.infinite=0
m=NMesh.GetRawFromObject(blenderObject.getName())
self.nmesh=m
#process verts
self.matrix=matrix
if (matrix):
self.rot=rotmatrix=matrix.rotationPart()
for v in m.verts:
self.verts.append(bVert(VecMultMat(Vector(list(v.co)+[1]),matrix),VecMultMat(Vector(list(v.no)),rotmatrix)))
else:
for v in m.verts:
self.verts.append(bVert(Vector(list(v.co)),Vector(list(v.no))))
#process faces
for f in m.faces:
if len(f.v)>2:
self.faces.append(bFace(f,self))
else:
#stuff related to infinite faces
1
#if there are just edges & not faces, make this face object infinite
def constructFaces(self,otherObject,destMesh,inside,outside,cosame,coopposing,flip=1):
print "Constructing Faces for ",self
for f in self.faces:
f.constructFace(destMesh,otherObject,inside,outside,cosame,coopposing,flip)
def MakeEdge(v0,v1,face,inout=0):
#Check if it already exists...
for e in v0.e:
if e.v[0]==v1 or e.v[1]==v1:
facenotfound=1
fdebug=[]
fentries=[]
for f in e.f:
if f[1]==face:
facenotfound=0
fdebug.append(f[0])
fentries.append(f)
if inout:
print "\t Altering Existing edge...maybe not a good idea",e.f
if facenotfound==0:
print "\t Prior state(s): ",fdebug, " keeping!"
return []
for f in fentries:
e.f.remove(f)
e.f.append([1,face])
e.f.append([2,face])
if facenotfound:
e.f.append([0,face])
return e
#if not, make a new edge
e=bEdge(v0,v1)
if inout:
e.f.append([1,face])
e.f.append([2,face])
else:
e.f.append([0,face])
face.parent.edges.append(e)
return e
def OverlayEdges(e1,e2):
return 1
###################################################################
# The Magic - the Intersection detector & processor #
###################################################################
def IntersectMeshes(meshA,meshB,doEdges=0):
global traversalCookie
global travBase
#with infinite meshes, we can't do bound box testing
if meshA.infinite or meshB.infinite:
skipbounds=1
else:
skipbounds=0
traversalCookie=traversalCookie+1
if doEdges==1:
travBase=traversalCookie
for e in meshA.edges:
travA=traversalCookie
eV=[]
for v in e.v:
#print v,v.travCookie,travBase
if v.travCookie0: #intersect somewhere
print "\tInterecting Edges...maybe"
#test crossing edges
if il[0]>-0.00001 and il[0]<1.00001 and il[1]>-0.00001 and il[1]<1.00001:
ip=e.v[0].tco+il[0]*e.vect
print "\t INTERSECT!!!",e,eA,ip,il[2]
normA=[]
normB=[]
nv=bVert(ip)
nv.sharedvert=1
for ef in e.f:
if e.f[0][1].parent==ef[1].parent: #Only original faces
ef[1].state=5
ef[1].extraVerts.append(nv)
nv.f.append(ef[1])
normA.append([ef[1].normal,ef[1].center,ef[1]])
for ef in eA.f:
if eA.f[0][1].parent==ef[1].parent:
ef[1].state=5
ef[1].extraVerts.append(nv)
nv.f.append(ef[1])
normB.append([ef[1].normal,ef[1].center,ef[1]])
print "\t Processing 1/2:",e,nv,normB
e.partition(nv,normB)
print "\t Processing 2/2:",eA,nv,normA
eA.partition(nv,normA)
skipnext=1
#test points in face
if 1 or skipnext==0:
for v in eV:
#if vert in face, mark its edges if they are at a sufficient angle with the face
if v.sharedvert==0 and TestPointInPlane(v.tco,f.normal,f.center) and TestPointInFace(v.tco,f):
#gotta test all edges against normal...
v.ApplyFaceIntersection(f)
skipnext=1
#edge already in face at tip? (damn nested loops... oh, well)
if skipnext==0:
for ev in e.v:
for ef in ev.f:
if ef==f:
skipnext=1
#test edge intersects face (only if edge doesn't lie in face)
if skipnext==0:
p=FindIntersectEdgeFace(e,f)
if p:
print "\tIntersection: ",p," in edge: ",e,e.v[0].tco,e.v[1].tco
nv=bVert(p)
nv.sharedvert=1 #mark vertex as shared by both objects
#attributes related to this faces
nv.f.append(f)
f.state=5 #mark as altered
f.extraVerts.append(nv)
#attributes related to edge's faces
for ef in e.f:
ef[1].state=5
#print "Applying to face ",ef
ef[1].extraVerts.append(nv)
nv.f.append(ef[1])
#partition the edge (make new edges)
e.partition(nv,[[f.normal,f.center,f]])
#if it got snapped to one of the verts, then the vert lies in the face...
if nv.GetRedirectTo()==e.v[0].GetRedirectTo():
e.v[0].ApplyFaceIntersection(f)
if nv.GetRedirectTo()==e.v[1].GetRedirectTo():
e.v[1].ApplyFaceIntersection(f)
#e.v[1].f.append(f)
###################################################################
# Base functions direction the operation #
###################################################################
def CookieCutter(cookie,cutter):
x=Draw.PupMenu("Faces to keep%t|Keep all|Inside only|Outside Only")
if x<1:
return
MatA=Matrix(*map(list,cookie.getMatrix()))
MatB=Matrix(*map(list,cutter.getMatrix()))
#MatB.invert()
#MatA=MatA*MatB
#MatB.identity()
bCookie=bMesh(cookie,MatA)
bCutter=bMesh(cutter,MatB)
IntersectMeshes(bCutter,bCookie,1)
print "###############################################################"
IntersectMeshes(bCookie,bCutter,0)
#IntersectMeshes(bCookie,bCutter,1)
print "###############################################################"
#IntersectMeshes(bCutter,bCookie,0)
nm=NMesh.New()
if x==1:
bCookie.constructFaces(nm,bCutter,1,1,1,1)
elif x==2:
bCookie.constructFaces(nm,bCutter,1,0,1,0)
elif x==3:
bCookie.constructFaces(nm,bCutter,0,1,0,1)
nm.mode=bCookie.nmesh.mode
NMesh.PutRaw(nm,"foo" )
return
def Difference(cookie,cutter):
MatA=Matrix(*map(list,cookie.getMatrix()))
MatB=Matrix(*map(list,cutter.getMatrix()))
#MatB.invert()
#MatA=MatA*MatB
#MatB.identity()
bCookie=bMesh(cookie,MatA)
bCutter=bMesh(cutter,MatB)
IntersectMeshes(bCookie,bCutter,1)
IntersectMeshes(bCutter,bCookie,0)
nm=NMesh.New()
bCutter.constructFaces(nm,bCookie,1,0,0,1,-1)
bCookie.constructFaces(nm,bCutter,0,1,0,0,1)
nm.mode=bCookie.nmesh.mode
NMesh.PutRaw(nm,"foo")
return
def Intersection(cookie,cutter):
MatA=Matrix(*map(list,cookie.getMatrix()))
MatB=Matrix(*map(list,cutter.getMatrix()))
#MatB.invert()
#MatA=MatA*MatB
#MatB.identity()
bCookie=bMesh(cookie,MatA)
bCutter=bMesh(cutter,MatB)
IntersectMeshes(bCookie,bCutter,1)
IntersectMeshes(bCutter,bCookie,0)
nm=NMesh.New()
bCutter.constructFaces(nm,bCookie,1,0,1,0)
bCookie.constructFaces(nm,bCutter,1,0,0,0)
#bCookie.constructFaces(nm,bCutter,0,1,0,0)
nm.mode=bCookie.nmesh.mode
NMesh.PutRaw(nm,"foo")
return
def Union(cookie,cutter):
MatA=Matrix(*map(list,cookie.getMatrix()))
MatB=Matrix(*map(list,cutter.getMatrix()))
#MatB.invert()
#MatA=MatA*MatB
#MatB.identity()
bCookie=bMesh(cookie,MatA)
bCutter=bMesh(cutter,MatB)
IntersectMeshes(bCookie,bCutter,1)
IntersectMeshes(bCutter,bCookie,0)
nm=NMesh.New()
bCutter.constructFaces(nm,bCookie,0,1,1,0)
bCookie.constructFaces(nm,bCutter,0,1,0,0)
nm.mode=bCookie.nmesh.mode
NMesh.PutRaw(nm,"foo")
return
def DoBool():
#Check the sanity of the selection
objsel=Object.GetSelected()
meshes=0
for ob in objsel:
if ob.getType() == 'Mesh':
meshes += 1
if meshes!=2 :
Draw.PupMenu("ERROR: Exactly two mesh objects must be selected")
return
x=Draw.PupMenu("MegaBool%t|1: Intersect|2: Union|3: Difference|4: Cookie Cutter")
#print x
if x==1:
Intersection(objsel[1],objsel[0])
elif x==2:
Union(objsel[1],objsel[0])
elif x==3:
Difference(objsel[1],objsel[0])
elif x==4:
CookieCutter(objsel[1],objsel[0])
DoBool()
#oA=Object.Get("Cookie")
#oB=Object.Get("Cutter")
#print oA
#print oB
#Intersection(Object.Get("Cookie"),Object.Get("Cutter"))
#Difference(Object.Get("Cookie"),Object.Get("Cutter"))
#Union(Object.Get("Cookie"),Object.Get("Cutter"))
#CookieCutter(Object.Get("Cookie"),Object.Get("Cutter"))
print "Done."
if len(messages)>0 and 0:
txt=Blender.Text.New("Boolean Messages")
txt.clear()
txt.write(messages)
#Note to self:
# Re-orient the messhes when doing a 2D cut so
#two-axis bounding box test can be done
#difference op should maybe just flip the normals of the B object and do an intersection
#
#For f-gons, don't cut with internal edges
#when making face do whole f-gon at once (so outer edge & inner loops)