#!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
08-Apr-2004 Rewrite of alot - completely changed how inside
vs outside determination works
""" __author__ = "Theodore K. Schundler" __version__ = "r10 8-Apr-05" __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="" enorm=0#1 drawxpt=0#1 xpt=[] ################################################################### # 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]-0.001>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): vectA=e.v[1].tco-e.v[0].tco dot = DotVecs(vectA,vectA) vectB=v.tco-e.v[0].tco dv=DotVecs(vectA,vectB) if (dv>=-0.0000001 and dv<=dot+0.0000001): vectA.normalize() vectB.normalize() dv2=DotVecs(vectA,vectB) if (dv2>0.99999999 and dv2<1.00000001): 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 SuperMakeLoop(start,vA,offset,stop,depth=0): if depth>999: print "ML(): loop too deep (maybe not a bug if the mesh is complicated)" return [] global traversalCookie start.travCookie=traversalCookie if start.v[0]==vA: vB=start.v[1] else: vB=start.v[0] eNormal=start.f[offset][1] face=start.f[offset][2] side=start.f[offset][0] print "ML():Making loop...",vA.tco,vB.tco,start,stop print "ML():\t",start.f bestE=[] bestAngle=-99 for e in vB.e: if e!=start: if e==stop: #all done! print "ML(): Loop Done!" start.f[offset][3]=1 return [side,[[start,vA]]] if e.v[0]==vB: vC=e.v[1] else: vC=e.v[0] if vC==vB: print "ML(): BUG: zero-length edge" dA=(vB.tco-vA.tco) dB=(vB.tco-vC.tco) if (dB.length<0.00001): print "ML(): BUG - zero length edge, separate verts - should have caught this earlier" MergeVert(vB,vC) #is there a way to do this w/o normalization? dA.normalize() dB.normalize() vsum=(dA+dB)/2 ang=DotVecs(dA,dB) if ang>0.9999: #this is a test for parallel edges -- see stuff in FindIntersectLineLine3D to avoid sqrt print "ML(): BUG? Edges folded over..." print "ML(): \t- ",start,start.v[0].tco,start.v[1].tco print "ML(): \t- ",e,e.v[0].tco,e.v[1].tco ang=-9999999 print "ML(): Option? ",vB.tco,"->",vC.tco,"(",ang,")" #Maybe add 180 to angle if new vect :dot: current vect's normal <0, then just find smallest angle? if ang>bestAngle: for fi in range(len(e.f)): f=e.f[fi] if f[2]==face and f[3]==0: print "ML(): Have a candidate...",vB.tco,"->",vC.tco,"(",ang,")" nsum=eNormal+f[1] nsl=nsum.length nsum.normalize() dp=DotVecs(nsum,vsum) dQ=DotVecs(nsum,dA) dW=DotVecs(nsum,dB) #note: the nsl>0.0001 thing is for dealing with edges where ang is 180 degrees (-1) if (ang>0 or nsl>0.0001) and (abs(dQ-dW)<0.001) and ang>bestAngle: #print "ML():\t",nsum,dA,dB #print "ML():\t\t",ang,dp,dQ,dW bestE=[e,fi,f[0],vC] bestAngle=ang if bestE and bestE[2]!=6: print "ML(): found next step",bestE[0],bestE[1],bestE[3].tco start.f[offset][3]=1 r=SuperMakeLoop(bestE[0],vB,bestE[1],stop,depth+1) if len(r)>1: if r[0]!=0 and r[0]!=5: if side!=0 and side!=5 and side!=r[0]: print "ML(): BUG??? State changed midloop from ",side,"to",r[0],"at",vB.tco if (side==0 or side==5): side=r[0] #if start.broken==0: # start.setSides(side) #if side!=0: # start.f[offset][0]=side lp=r[1] lp.append([start,vA]) return [side,lp] start.f[offset][3]=0 print "ML(): loop failed!" return [] ################################################################### # Pretty Quad Filler # ################################################################### #This may be a little faster if conversted to a 2D function using the desired #normal as the Z axis (doing a matrix transformation) #Add a face to an nmesh #Select verts as applicable def AddFace(oldFace,newMesh,vertexList,selected): vxl=[] for v in vertexList: if v.newCounterpart==0: v.newCounterpart=NMesh.Vert(v.tco[0],v.tco[1],v.tco[2]) newMesh.verts.append(v.newCounterpart) v.used=1 if selected: v.newCounterpart.sel=1 vxl.append(v.newCounterpart) newFace=NMesh.Face(vxl) if oldFace: if oldFace.counterPart: newFace.smooth=oldFace.counterPart.smooth newMesh.faces.append(newFace) def JoinConcentric(vertList,enclosed,CCWlist,allList,normal,f,nm): #Preffer parallel edges (smaller dot product absolute value) #something is wrong here... should be both close _and_ parallel #not just close... bestOuter=0 bestInner=0 parallelness=10000000 Ocount=len(vertList) for On in range(Ocount): I0=vertList[(On+Ocount-1)%Ocount].tco-vertList[On].tco; I1=vertList[(On+1)%Ocount].tco-vertList[On].tco; for vl in enclosed: Icount=len(vl) for In in range(Icount): vect=vertList[On].tco-vl[In].tco; d=vect.length; par=parallelness O0=vl[(In+Icount-1)%Icount].tco-vl[In].tco; O1=vl[(In+1)%Icount].tco-vl[In].tco; pt=(abs(DotVecs(I0,O0))+0.01)*d if (pt3: 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. Really just two times where 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 "FillLoops():\t\t\tFill Failed..." for v in l: print "FillLoops():\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 # 6 -> All done processing; ignore 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 "v.fS(): \t\t\tlame vert side look up -",self,self.tco #Note: Maybe it would be faster to find the closeest face, and use its normal # rather than doing an intersection test (_slightly_) less math 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 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 real(self): 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 self.sidescomputed=0 self.broken=0 self.linkto=0 bv0.e.append(self) bv1.e.append(self) print "@@@@Adding Edge... ",self,bv0.tco,bv1.tco def setSides(self,state): #Maybe this should make sure it only operates on original edges if state==0: print "e.sS(): BUG: Tried to set side state to 0" return if self.sidescomputed: return print "e.sS()",state for f in self.f: f[0]=state self.sidescomputed=1 def calcSides(self,force=0): #Maybe this should only be called onces per SuperMakeLoop? print "e.cS()",self if self.sidescomputed: return print "e.cS(): Compute Side: ",self if len(self.f)==2: if self.f[0][2]==self.f[1][2]: if self.f[0][0]: self.f[1][0]=self.f[0][0] self.sidescomputed=1 return elif self.f[1][0]: self.f[0][0]=self.f[1][0] self.sidescomputed=1 return for fA in self.f: if fA[0]==0 or fA[0]==5: closest=-1.1 fv=fA[1] for fB in self.f: if fB[2].parent!=fA[2].parent: d=DotVecs(fA[1],fB[1]) if d>closest: closest=d cf=fB if closest>-1.0: #print "e.cS(): ..." if closest>0.9999: print "e.cS(): COPLANAR FACES!!!!!" if DotVecs(fA[2].normal,cf[2].normal)>0: fA[0]=cf[0]=3 else: fA[0]=cf[0]=4 else: d=DotVecs(fA[1],cf[2].normal) #print "!!!!!!!!!!!this face:",fA,"other:",cf if (d>-0.0001 and d < 0.0001): print "e.cS(): Eeh??? BUG? Coplanar, but not?" elif d>0: fA[0]=1 else: fA[0]=2 #Can't judge other face's in/out automatically (probablems with "T" type setup, where one side has two coplanar faces) #d=DotVecs(cf[1],fA[2].normal) #if (d>-0.0001 and d < 0.0001): # print "Eeh??? BUG? Coplanar, but not?" #elif d>0: # cf[0]=1 #else: # cf[0]=2 else: print "e.cS(): Need to determine side recursively..." if not force: return 0 print "e.cS(): Computed Sides!",self.f self.sidescomputed=1 def testState(self,state,face): 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.allVerts=[] self.allEdges=[] self.center=Vector([0.0,0.0,0.0]) self.altered=0 self.built=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) self.normal.normalize() #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.allVerts.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 buildEdges(self): #print "f.bE():\tBuilding new edges for ",self,self.altered if self.altered: c=len(self.allVerts) print "f.bE():\tBuilding new edges for ",self," Verts:",c for A in range(c): for B in range(A+1,c): vA=self.allVerts[A] vB=self.allVerts[B] go=0 #Should an edge be made? mo=1 #Should this data be merged with an existing edge for fA in vA.f: if fA.parent!=self.parent: for fB in vB.f: if fB.parent!=self.parent: if fA==fB: d=DotVecs(fA.normal,self.normal) if (d>-0.9999 and d<0.9999): mo=0 go=1 if go: print "f.bE():\t\tNew Edge: ",A,vA.tco,B,vB.tco ne=MakeEdge(vA,vB,self,1,mo) #if ne: # ne.calcSides() def constructFace(self,otherObject,destMesh,inside,outside,cosame,coopposing,flip=1): #Should detect unaltered/simple quad/tri faces faster global enorm print "f.cf():\tConstructing face ",self if self.built: return #for v in self.allVerts: # print "f.cf():\t\t",v.tco loops=[[],[],[],[],[]] for e in self.allEdges: e.calcSides() for e in self.allEdges: sides=[] for f in e.f: if f[2]==self: sides.append([f[0],f[1]]) print "f.cf():\t\tE: ",e,e.v[0].tco,e.v[1].tco,sides for e in self.allEdges: print "f.cf():\t\tEdge: ",e,e.v[0].tco,e.v[1].tco if (abs(e.v[0].tco[0]-0.75)<0.1 and abs(e.v[1].tco[0]-1.0)<0.1 and abs(e.v[0].tco[1]+1.00)<0.1): print "#############################################" print e.f if (abs(e.v[1].tco[0]-0.75)<0.1 and abs(e.v[0].tco[0]-1.0)<0.1 and abs(e.v[0].tco[1]+1.00)<0.1): print "#############################################" print e.f #e.calcSides() if enorm==1: AddFace(self,destMesh,e.v,0) for f in e.f: #if 0: #draw edge normal pA=MidPoint(e.v[0].tco,e.v[1].tco) pB=pA+f[1]*0.05 if enorm==1: AddFace(self,destMesh,[NewVx(pA),NewVx(pB)],0) if len(e.children)>0 or e.linkto or e .broken: AddFace(self,destMesh,[NewVx(e.v[0].tco+f[1]*0.02),NewVx(e.v[1].tco+f[1]*0.02)],0) if f[2]==self and f[3]==0 and (f[0]>0 or f[0]<5): #NOTE: Maybe by using the edge normal to determine to use v[0] or v[1] #could be used to automatic normals l = SuperMakeLoop(e,e.v[0],e.f.index(f),e) if len(l): if l[0]>4: l[0]=4 print "f.cf():loop of type ",l[0] if l[0]==0: #Manually figure out side... l[0]=e.v[0].findSide(otherObject) print "f.cf(): Lame Lookup side: ",l[0] #There should problably be a better way of negotiating the side while traversing the edges... for le in l[1]: if le[0].broken==0: le[0].setSides(l[0]) loops[l[0]].append(l[1]) for q in l[1]: print "f.cf(): \t",q[1].tco if enorm==2: AddFace(self,destMesh,q[0].v,1) for ff in q[0].f: if ff[2]==self: pA=MidPoint(q[0].v[0].tco,q[0].v[1].tco) pB=pA+ff[1]*0.05 AddFace(self,destMesh,[NewVx(pA),NewVx(pB)],0) else: print "f.cf(): LOOP FAILED" if len(loops)==0: print "f.cf(): COULD NO FILL FACE",self return #NOTE: This can probably be done a little more efficiently now #that edge normals are precomputed and stored vxloops=[[],[],[],[],[],[]] for l in range (1,4): print "f.cf(): Loops of type ",l for lp in loops[l]: print "f.cf(): \tNL" nloop=[] for li in lp: nloop.append(li[1]) print "f.cf(): \t\t",li[1].tco vxloops[l].append(nloop) if outside: FillLoops(vxloops[1],self,destMesh,flip,0) if inside: FillLoops(vxloops[2],self,destMesh,flip,0) if cosame: FillLoops(vxloops[3],self,destMesh,flip,0) if coopposing: FillLoops(vxloops[4],self,destMesh,flip,0) self.built=1 #Go do other faces next for e in self.e: if e.broken==0: for f in e.f: if f[2].parent==self.parent and f[2].built==0: print "f.cf(): continuing to neighbor ",f f[2].constructFace(otherObject,destMesh,inside,outside,cosame,coopposing,flip) 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 "m.cF(): Constructing Faces for ",self for f in self.faces: if f.altered: f.constructFace(destMesh,otherObject,inside,outside,cosame,coopposing,flip) #return #take care of any loose ends in a 2nd pass for f in self.faces: if not f.altered: f.constructFace(destMesh,otherObject,inside,outside,cosame,coopposing,flip) def MakeEdge(v0,v1,face,inout=0,mergeonly=0): if v0==v1: print "MakeEdge(): BUG! Attempt to make edge pointing to self." return [] cve=v1.tco-v0.tco if cve.length<0.0001: print "MakeEdge(): BUG! Overlapping verticies",v0,v1,v0.tco,v1.tco return [] #Check if it already exists... ENorm=CrossVecs(face.normal,v1.tco-v0.tco) #ENorm=CrossVecs(v1.tco-v0.tco,face.normal) ENorm.normalize() for e in v0.e: if e.v[0]==v1 or e.v[1]==v1: for f in e.f: if f[2]==face: print "MakeEdge(): BUG??? Tried to add edge to face that already had it..." return e e.f.append([0,ENorm,face,0]) if inout: e.f.append([0,ENorm*(-1),face,0]) face.allEdges.append(e) return e #if not, make a new edge if not mergeonly: e=bEdge(v0,v1) e.f.append([0,ENorm,face,0]) if inout: e.f.append([0,ENorm*(-1),face,0]) face.parent.edges.append(e) face.allEdges.append(e) return e return [] ################################################################### # Merging Tools to keep things clean # ################################################################### def MergeVert(vA,vB): if vA==vB: return vA v=vA.tco-vB.tco #print "###################################################################" #print "MERGEVERTICIES: ",v.lenth,vA.tco,vB.tco for f in vB.f: if not vA in f.allVerts: f.allVerts.remove(vB) f.allVerts.append(vA) vA.f.append(f) if vB in f.v: f.v[f.v.index(vB)]=vA if vB in f.parent.verts: f.parent.verts[f.parent.verts.index(vB)]=vA for e in vB.e: if vB in e.v: e.v[e.v.index(vB)]=vA vA.e.append(e) for f in vA.f: f.altered=1 #test for merging edges for eA in vA.e: for eB in vA.e: if eA!=eB: if eA.v[0]==vA: rA=eA.v[1] else: rA=eA.v[0] if eB.v[0]==vA: rB=eB.v[1] else: rB=eB.v[0] if rA==rB: MergeEdge(eA,eB) dv=rA.tco-rB.tco if dv.length<0.0001: MergeVert(rA,rB) #if (eA.v[0]==eB.v[0] and eA.v[1]==eB.v[1]) or (eA.v[0]==eB.v[1] and eA.v[1]==eB.v[0]): # #print "EDGE OVERLAP",eA,eB return vA def MergeEdge(eA,eB): if eA==eB: return eA for v in eB.v: v.e.remove(eB) for f in eB.f: while eB in f[2].allEdges: f[2].allEdges.remove(eB) if not eA in f[2].allEdges: f[2].allEdges.append(eA) if eB in f[2].e: f[2].e[f[2].e.index(eB)]=eA while eB in f[2].parent.edges: f[2].parent.edges[f[2].parent.edges.index(eB)]=eA eA.f.append([0,f[1],f[2],0]) #eA.broken=1 #mark edge as altered eB.broken=1 eB.linkto=eA eB.f=[] # this shouldn't be necissary, but is for one of the test cases # it is probably indicative of a bug somewhere else return eA ################################################################### # Functions for Applying Intersection # ################################################################### def xptdraw(nm): global xpt,drawxpt if drawxpt: for pt in xpt: if pt[0]==0: p=pt[1] AddFace(0,nm,[NewVx(p+Vector(0.0,0.01,0.0)),NewVx(p-Vector(0.0,0.01,0.0))],0) AddFace(0,nm,[NewVx(p+Vector(0.01,0.0,0.0)),NewVx(p-Vector(0.01,0.0,0.0))],0) AddFace(0,nm,[NewVx(p+Vector(0.0,0.0,0.01)),NewVx(p-Vector(0.0,0.0,0.01))],0) if pt[0]==1: p=pt[1] AddFace(0,nm,[NewVx(p+Vector(0.007,0.007,0.0)),NewVx(p-Vector(0.007,0.007,0.0))],0) AddFace(0,nm,[NewVx(p+Vector(0.007,0.0,0.007)),NewVx(p-Vector(0.007,0.0,0.007))],0) AddFace(0,nm,[NewVx(p+Vector(0.0,0.007,0.007)),NewVx(p-Vector(0.0,0.007,0.007))],0) #new vertex for a given coordinate def NewVx(co): return bVert(co) def LinkVx(vA,vB): MergeVert(vA,vB) return 1 def CutEdge(e,co): #note: If an edge is cut, we can probably immediately mark inside vs outside # Problems only arise when edges overlap, which happens with vx in edge / vx in vx situations if e.linkto: print "CutEdge(): BUGBUGBUG!!!! Tried to cut inactive edge" return CutEdge(e.linkto,co) cv=co-e.v[0].tco #note: must scale by length so fuzzy matching is kept sane #note: maybe scaled point could be passed along, so there is no need # to do the dot product over & over again cutat=cv.length if (cutat<-0.0001 or cutat>e.len+0.0001): print "CutEdge(): BUG! Tried to cut edge out of bounds" if e.children: cd=cutat-e.cutPoint if cd<0.0001 and cd>-0.0001: #same point return e.children[0].v[1] elif cd>0: return CutEdge(e.children[1],co) else: return CutEdge(e.children[0],co) #test for cut point being at one of the two endpoints if cutat<0.0001: return e.v[0] elif cutat>(e.len-0.0001): return e.v[1] e.broken=1 #Edge actually needs to be partitioned print "CutEdge(): Partitioning Edge",e,"at",co,"(",cutat,"/",e.len,")" nv=NewVx(co) e.v[0].e.remove(e) e.v[1].e.remove(e) e.cutPoint=cutat A=bEdge(e.v[0],nv) B=bEdge(nv,e.v[1]) e.children.append(A) e.children.append(B) for f in e.f: nv.f.append(f[2]) f[2].allEdges.remove(e) f[2].allEdges.append(A) f[2].allEdges.append(B) B.f.append([0,f[1],f[2],0]) A.f.append([0,f[1],f[2],0]) f[2].allVerts.append(nv) f[2].altered=1 return nv def VxInFace(f,v): if not f in v.f: v.f.append(f) if not v in f.allVerts: f.allVerts.append(v) for vf in v.f: vf.altered=1 return 1 def VxInEdge(e,v): vp=CutEdge(e,v.tco) LinkVx(vp,v) return 1 ################################################################### # The Magic - the Intersection detector & processor # ################################################################### fxf=0 exf=0 vxf=0 exe=0 vxe=0 vxv=0 #sfxf=0 sexf=0 svxf=0 sexe=0 svxe=0 svxv=0 fexf=0 ffxf=0 fexe=0 def IntersectVertexVertex(vA,vB): global vxv,svxv vxv+=1 #d0=vA.tco[0]-vB.tco[0] #d1=vA.tco[1]-vB.tco[1] #d2=vA.tco[2]-vB.tco[2] #d=d0*d0+d1*d1+d2*d2 d=vA.tco-vB.tco if d.length<0.0001: #Verticies are effectively the same print "-VxV() Vertex overlap at ",vA.tco,vA,vB svxv+=1 LinkVx(vA,vB) for f in vA.f: f.altered=1 return 1 return 0 def IntersectVertexEdge(v,e): global vxe,svxe vxe+=1 i=0 #print "VxE",v.tco,e.v[0].tco,e.v[1].tco #Actually, maybe this loop should be _after_ TestVertInEdge3D for v2 in e.v: i+=IntersectVertexVertex(v,v2) if (i==0) and (TestVertInEdge3D(v,e)): print "-VxE() Vertex in edge at ",v.tco svxe+=1 nv=CutEdge(e,v.tco) LinkVx(nv,v) i=1 return i def IntersectEdgeEdge(eA,eB): global exe,fexe,sexe,xpt exe+=1 if TestBounds(eA.bb,eB.bb): fexe+=1 i=0 A=eA.vect B=eB.vect #used to be ,0,0 at end - edit if intersecting edges don't work r=FindIntersectLineLine3D(eA.v[0].tco,eA.v[1].tco,eB.v[0].tco,eB.v[1].tco,1,1) if len(r)==3: p=eA.v[0].tco+r[0]*A #test for vertex in edge for v in eA.v: i+=IntersectVertexEdge(v,eB) for v in eB.v: i+=IntersectVertexEdge(v,eA) if (i==0): print "-ExE(): Edge intersects edge at ",p xpt.append([1,p]) sexe+=1 nvA=CutEdge(eA,p) nvB=CutEdge(eB,p) LinkVx(nvA,nvB) i=1 else: A.normalize() B.normalize() dv=DotVecs(A,B) if (dv>0.999 or dv <0.999): #parallel? print "ExE(): Edges parallel" for v in eA.v: i+=IntersectVertexEdge(v,eB) for v in eB.v: i+=IntersectVertexEdge(v,eA) return i return 0 def IntersectVertexFace(v,f): #Note vertex may have already been added to #the face due to an intersection with an edge global vxf,svxf,xpt vxf+=1 #print "VxF",v.tco,TestPointInFace(v.tco,f) if TestPointInFace(v.tco,f) and TestPointInPlane(v.tco,f.normal,f.v[0].tco): i=0 for v2 in f.allVerts: i+=IntersectVertexVertex(v,v2) if (i==0): #Add point to face print "-VxF(): Point in face at ",v.tco,f, svxf+=1 VxInFace(f,v) xpt.append([1,v.tco]) return 1 return 0 def IntersectEdgeFace(e,f): global exf,fexf,sexf,xpt exf+=1 if TestBounds(e.bb,f.bb): fexf+=1 #Intersect? p=FindIntersectEdgeFace(e,f) if p: #print "FxE",p i=0 for eB in f.e: i+=IntersectEdgeEdge(e,eB) for v in e.v: i+=IntersectVertexFace(v,f) #iff vert doesn't lie in the object's face, we need to cut the edge if (i==0): #cut edge #note: at this point we know inside vs outside iff there isn's something screwy # further down the edge print "-ExF(): Face cuts Edge at ",p sexf+=1 nv = CutEdge(e,p) VxInFace(f,nv) xpt.append([0,nv.tco]) i=1 return i else: #Parallel? i=0 dv=DotVecs(e.v[0].tco-e.v[1].tco,f.normal) if (dv<0.00001 and dv>-0.00001): #print "Edge Parllel to face" for eB in f.e: i+=IntersectEdgeEdge(e,eB) for v in e.v: i+=IntersectVertexFace(v,f) return i return 0 def FullIntersect(meshA,meshB): #is this FacexFace method faster, or all edges vs all edges in two passes? global traversalCookie,fxf,ffxf traversalCookie+=1 travBase = traversalCookie print meshA,meshB #Iterate Face vs Face for fA in meshA.faces: traversalCookie+=1 fATrav = traversalCookie for fB in meshB.faces: traversalCookie+=1 fBTrav = traversalCookie fxf+=1 if TestBounds(fA.bb,fB.bb): ffxf+=1 #print "Possibly intersecting faces: ",fA,fB #fA.e vs fB -> can eliminate wth travcookie, test ExF, E.vxF, ExE, ExV, VxV print "eA vs fB" for eA in fA.e: if 1 or eA.travCookie must test all, only ExF? print "eB vs fA" for eB in fB.e: if 1 or eB.travCookie0 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)