#!BPY """ Name: 'MegaBool r15/pre1' Blender: 243 Group: 'Object' Tooltip: 'Perform boolean operations on meshes and get nice results.' """ __bpydoc__ ="""\
Experimental New Mesh Boolean Operations

(C) 2007 Theodore K Schundler (tschundler)(@)(gmail)(com)
FOR TESTING ONLY: Not production ready yet
Lastest version available for from:
http://epii.info/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.
Subsurf and Edge Split should be turned off before using this
script. (Actually any modifiers are best disabled.)

TO DO:
* Set vertex colors & groups & weights
* 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. (per discussion with toloban)
Also, 2nd object can now be a set of edge loops.
And 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-2005 Rewrite of alot - completely changed how inside
vs outside determination works
24-Apr-2005 Rewrite filling (handles complicated cases faster)
fixed dumb mistakes in intersecting edges, fixed
transform of vertex normals.
25-Apr-2005 Fixed compatability issues with released versions
of Blender for Vector() constructor.
02-May-2005 Fixed some issues with certain angles in building
loops. Better detection of intersecting edges.
Filling avoids making faces with an area of zero.
Debugging data doesn't display by default, so it
runs faster. Creates a new mesh instead of
writting over an old one.
14-Apr-2007 Updated with fixes from Henrik Pitkala, Hoehrer,
and intrr. This fixes some version compatability
issues. It makes the order and selection the same
as Blender's internal bools. Also, the default
recusion depth is increased - you can increase
it further by updating the max_recusion variable.
Lastly, I added progress bar support
21-Apr-2007 Faces now keep material and UVs.
02-Jun-2007 Rewrote intersection system again. New approach
splits things into tris first, avoiding nonplanar
quad problems, and laying the ground work for
f/n-gon support. """ __author__ = "Theodore K. Schundler" __version__ = "r15/pre1 02-Jun-2007" __email__ = ('Author EMail, tschundler:gmail*com') __url__ = ('Script Web Page, http://epii.info/oss/blender/boolean', 'BlenderArtists Thread, http://blenderartists.org/forum/showthread.php?t=28414') #Debugging Options showdbg = 0 #Spit out debugging info drawenorm = False #Draw Normals on Edges drawoutline = False #Draw outline of faces, but don't fill them drawxpt = False #Mark intersection points makenewobj = True #Make a new object rather than replacing an existing one usepsyco = False #Use Psyco acceleration if available fullpsyco = False #Apply acceleration to the full script max_recursion= 16384 #Increase this if you have recursion depth problems. #Initialize defaults traversalCrumb=0 messages="" xpt=[] 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 #Setup environment import Blender import sys import time from Blender import Object from Blender.Window import DrawProgressBar from Blender.Mathutils import * from Blender import Mesh from time import time if usepsyco: try: import psyco print "Using Psyco for script acceleration" except: usepsyco=0 sys.setrecursionlimit(max_recursion) #Match f'ns def TestIntersectRayFacet(r_point,r_direction,face,positive_only=0): global showdbg d=FindIntersectRayPlane(r_point,r_direction,face.e[0].v[0].co,face.normal,positive_only) if d==0: return 0 p=r_point+d*r_direction return TestPointInFacet(p,face) def FindIntersectRayPlane(r_point,r_direction,p_point,p_normal,positive_only=0): global showdbg d=DotVecs(p_normal,r_direction) if abs(d)<0.0000001: return 0 n=DotVecs(p_normal,p_point-r_point) nd=n/d if nd<0 and positive_only: return 0 return nd #same logic as Intersect Vert x Face def TestPointInFacet(pt,f): el = f.el #edge loop elend = el in_edge = None while 1: enorm = el.getENorm() dv = DotVecs(pt - el.e.v[0].co, enorm) if dv < 0: return False el = el.next if el==elend: break return True def BuildBasis(normal,edge): e2=CrossVecs(normal,edge) return [edge,e2] def Make2D(basis,vector): return Vector([DotVecs(basis[0],vector),DotVecs(basis[1],vector)]) #Note, there should probably be three versions of the loop that calls this #That way there isn't lots of unnecissary branching def Make2DwIX(index,normscale,vector): if normscale>0: if index==0: return Vector([vector[1],vector[2]]) if index==1: return Vector([vector[0],-vector[2]]) return Vector([vector[0],vector[1]]) else: if index==0: return Vector([vector[1],-vector[2]]) if index==1: return Vector([vector[0],vector[2]]) return Vector([vector[0],-vector[1]]) def xptdraw(nm): global showdbg global xpt,drawxpt if drawxpt: for pt in xpt: if pt[0]==0: p=pt[1] AddFace(0,nm,[bVert(p+Vector([0.0,0.01,0.0])),bVert(p-Vector([0.0,0.01,0.0]))],0) AddFace(0,nm,[bVert(p+Vector([0.01,0.0,0.0])),bVert(p-Vector([0.01,0.0,0.0]))],0) AddFace(0,nm,[bVert(p+Vector([0.0,0.0,0.01])),bVert(p-Vector([0.0,0.0,0.01]))],0) if pt[0]==1: p=pt[1] AddFace(0,nm,[bVert(p+Vector([0.007,0.007,0.0])),bVert(p-Vector([0.007,0.007,0.0]))],0) AddFace(0,nm,[bVert(p+Vector([0.007,0.0,0.007])),bVert(p-Vector([0.007,0.0,0.007]))],0) AddFace(0,nm,[bVert(p+Vector([0.0,0.007,0.007])),bVert(p-Vector([0.0,0.007,0.007]))],0) ################################################################### # Pretty Quad Filler v2 # ################################################################### #Objects class LoopElement: def __init__(self,co,link): global showdbg self.co=co self.link=link self.bb=0 self.next=0 def copy(self): global showdbg ne=LoopElement(self.co,self.link) ne.next=self.next return ne def SetNext(self,next): global showdbg self.next=next def GetNext(self): global showdbg return self.next def Reverse(self,first,prev): global showdbg next=self.next self.next=prev if next==first: next.next=self return next.Reverse(first,self) def GetCo(self): global showdbg return self.co def GetNextCo(self): global showdbg return self.next.co def GetVector(self): global showdbg return self.next.co-self.co def GetEdgeNormal(self): global showdbg return CrossVec2D(self.GetVector()) def TestEdgeIntersect(self,ptA,ptB,bb,stop): global showdbg if not self.bb: self.bb=BuildBounds2D(self.co) self.bb=ExpandBounds2D(self.bb,self.next.co) if TestBounds2D(self.bb,bb): if TestEdgeIntersectEdge2D(ptA,ptB,self.co,self.next.co): return True if self==stop: return False return self.next.TestEdgeIntersect(ptA,ptB,bb,stop) def nFill(self,n,tolerance,stop=0): global showdbg #print "nFill(",n,stop,")" if stop==self: return False if not stop: stop=self c=self ok=1 #test if we can make a convex loop for i in range(n-2): no=c.GetEdgeNormal() no.normalize() #print "\t",c.next.co,c.next.GetVector(),no #this can probably be optimized to jump past the convex point #maybe convex points can be cached? if DotVecs(c.GetEdgeNormal(),c.next.GetVector()) n) if self!=lastpt.next: if (not Over180(c.next.GetVector(),c.GetVector()*-1.0,c.next.GetEdgeNormal(),c.GetEdgeNormal())) and DotVecs(self.co-c.next.co,c.next.GetEdgeNormal())<0.00001+tolerance: #print "Breaks out of original poly (test 1)",self.co,c.next.co,c.next.GetEdgeNormal() return self.next.nFill(n,tolerance,stop) #ok, so it's convex. But does it intersect any existing edges? c=c.next if c!=self: prev=c c=c.next #print "cross?",prev.co,c.co #test through all the other points in the loop to make sure #our face doesn't enclose them (which would mean edges intersect somewhere) while c!=self: #print "\tcross?",c.co tp=self ok=0 #Note: should probably use and epsilon instead of zero. for i in range(n-1): if DotVecs(c.co-tp.co,tp.GetEdgeNormal())<0 or c.link==tp.link: ok=1 break tp=tp.next if not ok: if DotVecs(c.co-tp.co,CrossVec2D(self.co-tp.co))<0 or c.link==tp.link: ok=1 if not ok: #print "Cross Fail at ",c.co return self.next.nFill(n,tolerance,stop) prev=c c=c.next #print "no cross OK ------------------" #Test again that edge doesn't break out of loop if self!=lastpt.next: if (not Over180(-1.0*prev.GetVector(),self.GetVector(),prev.GetEdgeNormal(),self.GetEdgeNormal())) and DotVecs(lastpt.co-self.co,prev.GetEdgeNormal())<0.00001+tolerance: #print "Breaks out of original poly (test 2)",lastpt.co,self.co,prev.next.co,prev.co return self.next.nFill(n,tolerance,stop) nextnoprev=prev #make facet c=self facet=[] for i in range(n): #print "\t",c.co facet.append(c.link) prev=c c=c.next #do next self.next=prev #When moving on to the next step, take a step back if possible if self!=lastpt.next: res=nextnoprev.Fill() else: res=self.Fill() if res: res.append(facet) return res #for fault tolerance, something is returned if we got this far return [facet] else: #Apparently there is nothing else in the loop, so we're done c=self facet=[] for i in range(n): facet.append(c.link) c=c.next return [facet] def Fill(self): global showdbg #Maybe this would be better if it kept track of the loop size... #only or two vx? Then done. if self.next==self or self.next.next==self: #print self,"end" return [] #only three faces? if self.next.next.next==self: #print self,"end3" return [[self.link,self.next.link,self.next.next.link]] #try to fill using Quads ret=self.nFill(4,0.01) if ret: #print self,"quads.." return ret #else try tris ret=self.nFill(3,0.01) if ret: #print self,"tris.." return ret ret=self.nFill(4,0.0) if ret: #print self,"quads.. hightolerance" return ret #else try tris #print self,"high tolerance tris?" return self.nFill(3,0.0) def display(self,stop): global showdbg if showdbg: print "\t",self.co,self.link.tco if self==stop: return self.next.display(stop) class BSPElement2D: def __init__(self,pt,normal): global showdbg self.pt=pt self.normal=normal self.inside=0 self.outside=0 def TestPoint(self,pt): global showdbg return DotVecs(pt-self.pt,self.normal) def AddEdge(self,ptA,ptB,normal): global showdbg testA=self.TestPoint(ptA) testB=self.TestPoint(ptB) if testA<0 or testB<0 or (testA==0 and testB==0): if self.outside: self.outside.AddEdge(ptA,ptB,normal) else: self.outside=BSPElement2D(ptA,normal) if testA>0 or testB>0 or (testA==0 and testB==0): if self.inside: self.inside.AddEdge(ptA,ptB,normal) else: self.inside=BSPElement2D(ptA,normal) def TestPointRecursive(self,pt): global showdbg if self.TestPoint(pt)>0: if self.inside: return self.inside.TestPointRecursive(pt) else: return True else: if self.outside: return self.outside.TestPointRecursive(pt) else: return False #Expand Bounding Box def ExpandBounds2D(Bounds,v): for i in range(2): 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 CrossVec2D(v): return Vector([-v[1],v[0]]) def TestEdgeIntersectEdge2D(pA,pB,pC,pD): global showdbg vDC=pD-pC vBA=pB-pA Base=DotVecs(vBA,CrossVec2D(vDC)) if Base==0: #Parallel return False CvAC=CrossVec2D(pA-pC) uA=DotVecs(vDC,CvAC)/Base if uA>=0 and uA<=1: uB=DotVecs(vBA,CvAC)/Base if uB>=0 and uB<=1: return True return False #Determine if an angle should be regarded as > 180 degrees def Over180(VectorA,VectorB,EdgeVectorA,EdgeVectorB): #return DotVecs(VectorA+VectorB,EdgeVectorA+EdgeVectorB)<0 return DotVecs(VectorA,EdgeVectorB)<0 class VertexLoop: def __init__(self): global showdbg self.loopBase=self.loopLast=0 self.enclosed=[] self.BSPBase=0 #Adding points to the loop def AddPoint(self,co,link): global showdbg if self.loopLast: ne=LoopElement(co,link) self.loopLast.SetNext(ne) self.loopLast=ne self.bb=ExpandBounds2D(self.bb,co) else: self.loopBase=self.loopLast=LoopElement(co,link) self.bb=BuildBounds2D(co) def DonePoints(self): global showdbg self.loopLast.SetNext(self.loopBase) self.loopBase=self.loopBase.next #Are the verts ordered in the right direction for an outer loop? def IsWrongDirection(self): global showdbg #Find the leftmost point on the object leftmost=self.loopBase lp=leftmost.GetNextCo() cb=self.loopBase.GetNext() while cb!=self.loopBase: if cb.GetNextCo()[0]0 or DotVecs(refVect,leftmostnext.GetEdgeNormal())>0: return True else: return False else: if DotVecs(refVect,leftmost.GetEdgeNormal())>0 and DotVecs(refVect,leftmostnext.GetEdgeNormal())>0: return True else: return False def Reverse(self): global showdbg self.loopBase.Reverse(self.loopBase,0) #BSPs for determining loop heirarchy def BuildBSP(self): global showdbg current=stop=self.loopBase self.BSPBase=BSPElement2D(current.GetCo(),current.GetEdgeNormal()) while (current.GetNext()!=stop): current=current.GetNext() self.BSPBase.AddEdge(current.GetCo(),current.GetNextCo(),current.GetEdgeNormal()) def TestLoopInLoop(self,other): global showdbg if not TestBounds2D(self.bb,other.bb): return False if not self.BSPBase: self.BuildBSP() return self.BSPBase.TestPointRecursive(other.loopBase.GetCo()) def TestEdgeIntersect(self,pA,pB): global showdbg bb=BuildBounds2D(pA) bb=ExpandBounds2D(bb,pB) self.loopBase.GetNext().TestEdgeIntersect(pA,pB,bb,self.loopBase) def JoinLoop(self,other): global showdbg #Reasonably big distance - bigger than anithing inside the shape dist=(self.bb[1][0]-self.bb[0][0])*(self.bb[1][1]-self.bb[0][1]) pair=[] Ostop=Ocurrent=self.loopBase.GetNext() prevVect=self.loopBase.GetVector()*(-1) prevNorm=self.loopBase.GetEdgeNormal() Istop=other.loopBase while (1): Icurrent=Istop while (1): Icurrent=Icurrent.GetNext() vect=Icurrent.GetCo()-Ocurrent.GetCo() evect=Ocurrent.GetVector() enorm=Ocurrent.GetEdgeNormal() o180=Over180(evect,prevVect,enorm,prevNorm) #distance is less than current winner? d=DotVecs(vect,vect) #print "Distance: ",d if d0 or DotVecs(vect,prevNorm))) or ((not o180) and (DotVecs(vect,enorm)>0 and DotVecs(vect,prevNorm))): #And not intersect with anything? if not self.TestEdgeIntersect(Icurrent.GetCo(),Ocurrent.GetCo()): #And not with any other loops: ok=1 for lp in self.enclosed: if lp.TestEdgeIntersect(Icurrent.GetCo(),Ocurrent.GetCo()): ok=0 break if ok: #print "Winner: ",d dist=d pair=[Icurrent,Ocurrent] if Icurrent==Istop: break prevVect=evect*(-1) prevNorm=enorm Ocurrent=Ocurrent.GetNext() if Ocurrent==Ostop: break #success? if pair: #keep the edges going in the right direction... Icurrent=pair[0] Ocurrent=pair[1] #print "I/O",Icurrent.GetCo(),Ocurrent.GetCo() other.Reverse() #Connect the two loops Icopy=Icurrent.copy() Ocopy=Ocurrent.copy() Ocurrent.SetNext(Icopy) Icurrent.SetNext(Ocopy) return True if showdbg: print "ERROR: Could not connect loops." return False #Recursive filling! def Fill(self): global showdbg facets=[] #print "----Fill Loops: ",self,facets #If there are loops encompased by this loop, they must be done first if (self.enclosed): #If there are any loops inside this loop, fill then first for eeloop in self.enclosed[0].enclosed: #print "Fill enc" facets.extend(eeloop.Fill()) #Get on with the connecting... self.JoinLoop(self.enclosed[0]) print "Fill(): Connect:",self.loopBase.co,self.enclosed[0].loopBase.co self.enclosed.remove(self.enclosed[0]) facets.extend(self.Fill()) return facets #self.Display() res=self.loopBase.Fill() if res: facets.extend(res) return facets #Figure out loop directions and such, building up loop objects #Input: loops - an array of arrays of objects with a ".co" attribute # norm - Face normal #Ouput: array of arrays or arrays [EdgeNormal,Coordinate,LinkToOriginal] def BuildLoops(loops,norm): global showdbg #Re-orient points so we can safely use just X&Y coordinates # (note: if Normal *dot* <0,0,1> is >>0, this isn't really necissary) #Basis=BuildBasis(norm,loops[0][1].tco-loops[0][0].tco) normix=0 dirscale=abs(norm[0]) if abs(norm[1])>dirscale: normix=1 dirscale=abs(norm[1]) if abs(norm[2])>dirscale: normix=2 if norm[normix]>0: normscale=1.0 else: normscale=-1.0 NewLoops=[] for loop in loops: #Build Loop Content NL=VertexLoop() for pt in loop: #NL.AddPoint(Make2D(Basis,pt.tco),pt) NL.AddPoint(Make2DwIX(normix,normscale,pt.co),pt) NL.DonePoints() #NL.Reverse() #Fix direction if need be if NL.IsWrongDirection(): if showdbg: print "loop at ",NL.loopBase.co," is backwards" NL.Reverse() NewLoops.append(NL) return NewLoops #Make loops into a heirarchy of loops def BuildHierarchy(loops): global showdbg for loopA in loops: for loopB in loops: if loopA!=loopB: if loopA.TestLoopInLoop(loopB): loopA.enclosed.append(loopB) loopA.enclosed=BuildHierarchy(loopA.enclosed) loops.remove(loopB) return BuildHierarchy(loops) return loops def BuildFacets(loops,normal): global showdbg #Build up loops in the structure we need, and make sure #they are going the right direction newloops=BuildLoops(loops,normal) #if their are multiple loops, make a loop heirarchy if len(newloops)>1: newloops=BuildHierarchy(newloops) facets=[] for loop in newloops: r=loop.Fill() if showdbg: print r if r: facets.extend(r) return facets def FillLoops(loops,oldFace,newMesh,flip,selected): global showdbg #Make loops of verts vxloops = [] for loop in loops: vxloop = [] e = loop elend = e while 1: vxloop.append(e.getL2NextVert()) e = e.l2_next if e == elend: break vxloops.append(vxloop) if vxloops==[]: return global drawoutline if drawoutline: for loop in vxloops: c = len(loop) for i,v in enumerate(loop): AddFace(oldFace, newMesh, [v,loop[(i+1)%c]], 1) return fl=BuildFacets(vxloops,oldFace.facets[0].normal*flip) for facet in fl: AddFace(oldFace,newMesh,facet,selected) def AddFace(oldFace,newMesh,vertexList,selected=False): global showdbg vxl=[] for v in vertexList: if not v.newCounterpart: newMesh.verts.extend(v.co) v.newCounterpart = newMesh.verts[len(newMesh.verts)-1] v.used=1 if selected: v.newCounterpart.sel=1 vxl.append(v.newCounterpart) if len(vxl) == 2: newMesh.edges.extend(vxl[0], vxl[1]) else: #newFace=NMesh.Face(vxl) newMesh.faces.extend(vxl) nf = newMesh.faces[len(newMesh.faces)-1] if oldFace: 1 #copy attributes ES_UNKNOWN = 0 ES_OUTSIDE = 1 ES_INSIDE = 2 ES_COSAME = 3 ES_COOPPO = 4 ES_CUTFACE = 5 #unused? ES_DONE = 6 #unused? #to get "break 2" type behavior in an awkward as all get out python class BreakLoop(Exception): pass #Maybe mark BB done and add epsilon? Or add it on each vert add? class BoundingBox: def __init__(self, v): self.bb=[] for pt in v: self.bb.append([pt,pt]) def expand(self, v): for i,b in enumerate(self.bb): if (b[0] > v[i]): b[0] = v[i] elif (b[1] < v[i]): b[1] = v[i] return self #Tolerance Should be a little bigger than the default epsilon used later def test(self, other, tolerance = 0.0005): for i,b in enumerate(self.bb): o = other.bb[i] if b[0]-tolerance > o[1] or o[0]-tolerance > b[1]: return False return True def testpt(self, pt, tolerance = 0.0005): for i,b in enumerate(self.bb): o = pt[i] if b[0] - tolerance > o or o - tolerance > b[1]: return False return True class bVert: def __init__(self, co, original = None): self.original = original self.new = None self.co = co self.e = [] self.shared = False self.newCounterpart = None self.merged = False def findEdge(self, other): for e in self.e: if e.getOtherVert(self) == other: return self def findSide(self, otherObject): global showdbg if showdbg: print "v.fS(): \t\t\tlame vert side look up -",self,self.co #Note: Maybe it would be faster to find the closest face, # (vector projection based's equation for the face, then test # if perpenduclar ray from face to pt lies in face. If not, # use closest vertex in the face.) ray=Vector([1,0,0]) ifaces=0 for f in otherObject.faces: for ft in f.facets: if TestIntersectRayFacet(self.co,ray,ft,1): ifaces+=1 if (ifaces%2)==1: return 2 else: return 1 #Faces have many facets. #facets are flat surfaces (share the same normal) #facets are convex (could use BSP, but it's probably more work than worth it) #whereas faces may not always be flat or convex class bFacet: def __init__(self, edges, face): global traversalCrumb self.altered = False self.traversalCrumb = traversalCrumb self.face = face #needed for calc sides to find mesh shared = edges[0].getSharedVert(edges[1]) self.normal = CrossVecs(edges[1].getOtherVert(shared).co - shared.co,edges[0].getOtherVert(shared).co - shared.co) self.normal.normalize() #normalized to make it easy to calc distance of point from plane self.e = edges self.ell = [] for e in edges: bEdgeFaceLink(e,self) #This sets up the self.ell entry for i,el in enumerate(self.ell): el.setNext(self.ell[(i+1)% len(edges)]) el.assignLoop(self.ell[0]) self.el = self.ell[0] self.loops = [self.el] def markAltered(self): self.altered = True def testAltered(self): return self.altered class bFace: def __init__(self, mesh, original): global showdbg self.original = original self.mesh = mesh self.edges = [] self.allVerts = [] self.altered = False self.built = False for e in original.edge_keys: be = mesh.getEdge(e[0], e[1]) #be.addFace(self) self.edges.append(be) for i,v in enumerate(original.verts): vp = mesh.verts[v.index] self.allVerts.append(vp) if i == 0: self.bb = BoundingBox(vp.co) else: self.bb.expand(vp.co) self.facets = [] if len(original.v) == 3: nf = bFacet(self.edges, self) self.facets.append(nf) else: #For now, all quads are two tris v0 = mesh.verts[original.v[0].index] v1 = mesh.verts[original.v[2].index] fe = bEdge(v0, v1, None, False) nf = bFacet([self.edges[0],self.edges[1],fe], self) self.facets.append(nf) nf = bFacet([fe, self.edges[2],self.edges[3]], self) self.facets.append(nf) def constructFace(self,altered, otherObject,destMesh,inside,outside,cosame,coopposing,flip=1): global showdbg if showdbg: print "f.cf():\tConstructing face ",self #if self.built: # return loops=[[],[],[],[],[]] for f in self.facets: #print altered,f.testAltered() if altered == f.testAltered(): #if True == altered: fl = [] #if f.altered: # print "Altered" #else: # print "Same" #print "Handle facet",f for e in f.loops: #todo: Join applicable facets if edge has # exactly two faces, same side + same face # the join. If both halfedges are in different # loops, join to one loop. If halfedges are # from the same loop, split into two loops #print " +-- Go loop",e elend = e state = ES_UNKNOWN while 1: s = e.e.calcSides() if e.state != ES_UNKNOWN: state = e.state if not drawenorm: break #AddFace(None, destMesh, [e.e.v[0],e.e.v[1]]) nv = e.getL2NextVert() pv = e.e.getOtherVert(nv) #print " +-- Add Edge ", e,pv.co, nv.co #print " +-- loop entry", e.f, e.loopstart, e.e if drawenorm: edge_dir = nv.co-pv.co edge_dir.normalize() enorm = e.getENorm(); edge_angle = enorm - edge_dir mid =(e.e.v[0].co + e.e.v[1].co)*0.5 for i in range(e.state+1): AddFace(None, destMesh, [bVert(mid+edge_dir * (i*0.025)),bVert(mid+edge_angle * 0.05+edge_dir * (i*0.025))]) fl.append(e) e = e.l2_next if e == elend: break if e in fl: print "NON TERMINATING LOOP - next: ",e raise Exception if state == ES_UNKNOWN: if showdbg: print "Unknown side - traverse surface?" state = e.recursiveSideLookup() if state == ES_UNKNOWN: if showdbg: print "Unknown side - needs manual lookup" state = e.e.v[0].findSide(otherObject) if showdbg: print "result: ", state loops[state-1].append(e) #loops found, now fill them if outside: FillLoops(loops[0], self, destMesh, flip, 0) if inside: FillLoops(loops[1], self, destMesh, flip, 0) if cosame: FillLoops(loops[2], self, destMesh, flip, 0) if coopposing: FillLoops(loops[3], self, destMesh, flip, 0) self.built = True class bEdgeFaceLink: def __init__(self,edge,facet): self.e = edge self.f = facet facet.ell.append(self) self.enorm = None self.state = ES_UNKNOWN edge.addFacet(self) #l2 next/prev are for building edge loops self.l2_next = None self.l2_prev = None self.loopstart = None def setNext(self, next): self.next = next self.l2_next = next next.l2_prev = self self.l2_vert = self.getNextVert() def getNextVert(self): return self.e.getSharedVert(self.next.e) def getL2NextVert(self): if self.e == self.l2_next.e: if self.e == self.l2_prev.e: #loop has only one edge return self.e.v[self.l2_vert] return self.e.getOtherVert(self.e.getSharedVert(self.l2_prev.e)) return self.e.getSharedVert(self.l2_next.e) def printLoop(self): el = self elend = el print " Loop: ",self.loopstart while 1: v = el.getL2NextVert() print " ",v,el.e,v.co,el el = el.l2_next if el == elend: break def reverseLoop(self): self._reverseLoop(self) def _reverseLoop(self, stop): next = self.l2_next self.l2_next = self.l2_prev self.l2_prev = next if next == stop: return return next._reverseLoop(stop) def initLoop(self, other): #print "Init loop",self self.l2_next = self.l2_prev = other other.l2_next = other.l2_prev = self self.l2_vert = 0 other.l2_vert = 1 self.loopstart = self other.loopstart = self def reassignLoop(self, loop): if len(self.e.children): print "BUG: reassigning loops using a parent edgeloop entry" elend = self el = self while 1: #if el.loopstart == el: # print "Reassign ",el," to ",loop el.loopstart = loop el = el.l2_next if el == elend : break def setL2Next(self, next): self.l2_next = next next.l2_prev = self def setEdge(self, e): self.e = e def assignLoop(self, loopstart): self.loopstart = loopstart def getLoop(self): return self.loopstart def getENorm(self): #Should it always be calculated? Something to check in the C version for impact #Or maybe calc on FxF - if BB hit, then make sure enorms are set up if self.enorm: return self.enorm #Note: here is another candidate for the edge ray sv = self.getNextVert() if not sv: #eh? There's no shared vert? print "My Edge:",self.e," their edge",self.next.e print "My Verts:",self.e.v[0],self.e.v[0].co,self.e.v[1],self.e.v[1].co print "Their Verts:",self.next.e.v[0],self.next.e.v[0].co,self.next.e.v[1],self.next.e.v[1].co raise Exception eray = self.e.getOtherVert(sv).co - sv.co self.enorm = CrossVecs(eray,self.f.normal) #normalized for distance of vert from edge calc without extra dot product #Whether this is too expensive needs to be explored in the C version self.enorm.normalize() return self.enorm def assignLoopState(self, state): el = self elend = el while 1: el.state = state el = el.l2_next if el == elend: return #travel along the object's surface to find other faces whose side may have already been determined def recursiveSideLookup(self): global traversalCrumb traversalCrumb += 1 return self._recursiveSideLookup(traversalCrumb) def _recursiveSideLookup(self,tc): el = self elend = el state = self.state el.f.traversalCrumb = tc while state == ES_UNKNOWN: if el.state != ES_UNKNOWN: state = el.state elif len(el.e.f) == 2: if el.e.f[0] == el: dl = el.e.f[1] else: dl = el.e.f[0] if dl.f.traversalCrumb < tc: state = dl._recursiveSideLookup(tc) el = el.l2_next if el == elend: break if state !=ES_UNKNOWN: self.assignLoopState(state) return state class bEdge: #Maybe edge ray should be stored in edge? def __init__(self, v1, v2, original = None, real = True): global traversalCrumb self.traversalCrumb = traversalCrumb #print "Add Edge",v1.co,v2.co self.original = original self.v = [v1,v2] self.f = [] #Basically a radial edge loop like in bmesh self.real = real v1.e.append(self) v2.e.append(self) self.children = [] self.cutpoint = -100000 self.cutparent = None self.sidescomputed = False def getAllVerts(self): v = [self.v[0]] for c in self.children: v.extend(c.getAllVerts()) v.append(self.v[1]) return v def addFacet(self, facet): self.f.append(facet) def replaceVert(self, prev, new): if self.children: print "BUG? replace verts in edge that has been split" raise Exception if self.cutparent: print "replacing verts in child... potentially problematic - you can probably ignore a subsequent message about merging shared edges" cp = self.cutparent while cp.cutparent: cp = cp.cutparent return cp._replaceVert(prev,new) return self._replaceVert(prev, new) def _replaceVert(self,prev,new): #print "EE Replace verts in ",self if prev in self.v: ix = self.v.index(prev) self.v[ix] = new if len(self.children): #If first or last, only follow applicable branch return self.children[ix]._replaceVert(prev,new) for c in self.children: #vert shared between children... could maybe figure out which child using bounds or dot product c._replaceVert(prev, new) def getOtherVert(self, vert): if self.v[0] == vert: return self.v[1] else: return self.v[0] def getSharedVert(self, other): for vA in self.v: for vB in other.v: if vA==vB: return vA def fixLoop(self, facet, el0, el1): if self.children: print "BUG! Call fixLoop() on parent edge instead of child." for v in self.v: #find a pair pf edges that this edge is inbetween if showdbg: print "Fix loops at vert",v,v.co OK = False try: for e in v.e: if e!=self: for el in e.f: if el.f == facet and showdbg: print "fL() candidate: ",el,el.f,el.l2_next #Note: if edge loops back on itself, get shared vert might not work right if el.f == facet and el != el0 and el!= el1 and (el.getL2NextVert() == v): #print " +-- Pass 1" ### # Maybe this would be better testing the position of the other verts vs. the new edge's normals # ....though there's still the >180 degrees problem... prev_el = el next_el = el.l2_next prev_v = el.e.getOtherVert(v) next_v = el.l2_next.e.getOtherVert(v) #maybe >180 should be precomputed? new_ray = self.getOtherVert(v).co - v.co if prev_el.e == next_el.e: #print " +-- Pass auto" #Edge loops back on itself -> no other candidates if showdbg: print " +-- PASS AUTO loops",prev_el.e,prev_el,next_el print " - loops ",prev_el.getLoop(),el0.getLoop(),el1.getLoop(),next_el.getLoop() OK = True else: o180 = DotVecs(next_v.co - v.co, prev_el.getENorm()) if (o180 > 0): #less than 180 degrees - test against both normals if DotVecs(new_ray, prev_el.getENorm()) > 0: if DotVecs(new_ray, next_el.getENorm()) > 0: OK = True if OK: if showdbg: print " +-- pass < 180",v.co elif showdbg: print " X-- fail < 180" else: #over 180 degrees - either normal if DotVecs(new_ray, prev_el.getENorm()) > 0: OK = True elif DotVecs(new_ray, next_el.getENorm()) > 0: OK = True if OK: if showdbg: print " +-- pass > 180",v.co elif showdbg: print " X-- fail > 180" if OK: #We have a winner #Need to figure out which side to join with with edge prev_ray = prev_v.co - v.co dRR = DotVecs(prev_ray, new_ray) #ray vs ray dNN = DotVecs(prev_el.getENorm(), el0.getENorm()) #normal vs normal new_next = el1 new_prev = el0 #perhaps better tolerance is the smaller of prev.prev^2 vs new.new^2 if abs(dRR) > 0.00000000001 and abs(dNN) > 0.00001: #not perpendicular if showdbg: print " +-- OK / not perpendicular",v.co, dRR, dNN if (dRR < 0 and dNN > 0) or (dRR > 0 and dNN < 0): new_next = el0 new_prev = el1 else: #if perpendicular test normals vs rays from each other dRN0 = DotVecs(prev_ray, el0.getENorm()) dRN1 = DotVecs(new_ray, prev_el.getENorm()) if showdbg: print " +-- OK / perp",v.co, dRN0, dRN1,"(",dRR,dNN,")" if (dRN0 < 0 and dRN1 < 0) or (dRN0 > 0 and dRN1 > 0): new_next = el0 new_prev = el1 #print "reloop",new_prev, next_el, prev_el, new_next if new_prev.l2_next != new_next: #What causes this? if new_next.loopstart == next_el.loopstart: print "BUG! Joining same loop, but different directions" print " WAS:",prev_el,next_el,prev_el.getL2NextVert() print " INS:",new_next,new_prev,new_next.getL2NextVert() print " inxt:",new_next.l2_next,new_prev.l2_next print " iprv:",new_next.l2_prev,new_prev.l2_prev el = prev_el elend = prev_el me = Mesh.New("mm") lv = el.e.getOtherVert(el.getL2NextVert) print " st: ",lv,lv.co while 1: vvv = el.getL2NextVert() AddFace(0,me,[lv,vvv],0) print " * ",el,vvv,vvv.co el = el.l2_next lv = vvv if el == elend: break o = Blender.Scene.GetCurrent().objects.new(me, "mm") raise Exception #This fix is invalid t = new_next new_next = new_prev new_prev = t else: new_prev.reverseLoop() #if new_next.loopstart == next_el.loopstart: # print "nofix" reloop = False if new_next.loopstart == next_el.loopstart: if showdbg: print "Joining loop to itself => need to make new loop" reloop = True else: if new_prev.loopstart in facet.loops: #may not have been added yet facet.loops.remove(new_prev.loopstart) if showdbg: print "Join ",new_prev.loopstart," to ",next_el.loopstart new_prev.reassignLoop(next_el.loopstart) new_prev.setL2Next(next_el) prev_el.setL2Next(new_next) if reloop: if next_el.loopstart != prev_el.loopstart: print "WTF... edges in same loop w/ different starts",next_el.loopstart, prev_el.loopstart loop = next_el.loopstart if showdbg: print "Current :",facet.loops print "Shared: ",next_el.loopstart, prev_el.loopstart print "Split",loop,"into",next_el,prev_el facet.loops.remove(loop) next_el.reassignLoop(next_el) prev_el.reassignLoop(prev_el) if next_el in facet.loops: #Shouldn't already be there raise Exception facet.loops.append(next_el) if prev_el in facet.loops: #Shouldn't already be there print facet.loops raise Exception facet.loops.append(prev_el) if next_el.loopstart == prev_el.loopstart: print "BUG! Loops same (",next_el.loopstart,") after reassignment" raise BreakLoop #Break out of loop to move to the next vert except BreakLoop: if showdbg: print "Join Loops for",facet,"OK at",v.co,prev_v.co,next_v.co pass if not OK: if showdbg: print "Could not find edge to link to",v.co,"in",facet #if not el0.l2_next and not el1.l2_next: # facet.loops.append(el1) # el0.assignLoop(el1) # el1.assignLoop(el1) #if not el0.l2_next: # el0.setL2Next(el1) # if showdbg: # print "loop",el0,el1,"in",facet #if not el1.l2_next: # el1.setL2Next(el0) # if showdbg: # print "loop",el1,el0,"in",facet #if loops back on itself, it should be added to the main edge loop list if el0.l2_next == el0.l2_prev and el1.l2_next == el1.l2_prev: if showdbg: print "Add self loop: ",el0,el0.loopstart if el0.loopstart in facet.loops: #Shouldn't already be there raise Exception facet.loops.append(el0.loopstart) def cut(self, pt, vert = None): if self.cutparent: print "BUG!BUG!BUG! Cut child instead of parent" return self.cutparent.cut(pt, vert) dist = DotVecs(pt - self.v[1].co, pt - self.v[1].co) if showdbg: print "@#@#@#@#@#@#@# CUT: ",self,dist return self._cut(pt, dist, vert) def _cut(self, pt, dist, vert): global vxv, svxv #Branch to children if applicable if len(self.children): if dist > self.cutpoint: return self.children[0]._cut(pt,dist, vert) else: return self.children[1]._cut(pt,dist,vert) #Check for merging v0 = pt - self.v[0].co vxv += 1 dist0 = DotVecs(v0,v0) #print "dist0: ",dist0, self.v[0].co if dist0 < 0.0000001: #Distance (0.0003) squared....maybe faster to test each axis separately? svxv +=1 #need to merge verts if vert and self.v[0]!=vert: MergeVert(self.v[0], vert) return self.v[0] v1 = pt - self.v[1].co vxv +=1 dist1 = DotVecs(v1,v1) #print "dist1: ",dist1, self.v[1].co if dist1 < 0.0000001: #Distance squared....maybe faster to test each axis separately? svxv +=1 #need to merge verts if vert and self.v[1]!=vert: MergeVert(self.v[1], vert) return self.v[1] #No merge means we need to subdivide vtemp = vert vert = bVert(pt) self.cutpoint = dist if showdbg: print "CutEdge(): Partitioning Edge",self,"at",pt,"with",vert self.v[0].e.remove(self) self.v[1].e.remove(self) A = bEdge(self.v[0], vert) #maybe if using an existing vert, it could check for an existing edge? A.cutparent = self B = bEdge(vert, self.v[1]) B.cutparent = self self.children = [A,B] for efl in self.f: efl.f.ell.remove(efl) flA = bEdgeFaceLink(A,efl.f) flB = bEdgeFaceLink(B,efl.f) flA.enorm = efl.getENorm() flB.enorm = efl.getENorm() #need to set next/prev based on loop direction if A.getSharedVert(efl.l2_next.e): flB.setL2Next(flA) flA.setL2Next(efl.l2_next) efl.l2_prev.setL2Next(flB) else: flA.setL2Next(flB) flB.setL2Next(efl.l2_next) efl.l2_prev.setL2Next(flA) if efl.loopstart == efl: #we are about to remove an edge that marks the start of a loop #loopstart must be reassigned efl.f.loops.remove(efl) if flA in efl.f.loops: #Shouldn't already be there raise Exception efl.f.loops.append(flA) flA.reassignLoop(flA) else: #otherwise, keep in same loop as parent flA.assignLoop(efl.loopstart) flB.assignLoop(efl.loopstart) if vtemp: MergeVert(vtemp, vert) vert = vtemp return vert #This is being really slow def calcSides(self): global showdbg if self.sidescomputed: if showdbg: print "e.CS(",self,"): Request to compute sides on edge already done" return if showdbg: print "e.CS(",self,"): start" if len(self.f) == 2: #futile on edge with only two faces self.sidescomputed = 1 return #Possible check: if # of sides is odd, and > 1, then there is # an awkward non-manifold situation #if only two links in same facet, and other facet has side, use that for elA in self.f: if elA.state == ES_UNKNOWN or elA.state == ES_CUTFACE: closest = -1.1 cf = None for elB in self.f: if elA.f.face.mesh != elB.f.face.mesh: d = DotVecs(elA.getENorm(), elB.getENorm()) if d > closest: closest = d cf = elB if cf: #This second dot vecs shouldn't be needed #something is wrong here it seems. miscalculated edge normals? if closest > 0.9999 or DotVecs(cf.f.normal, elA.f.normal) > 0.9999: if showdbg: print "e.CS(): COPLANAR FACES!!!" #WARNING! This is error-prone. Really the full loop should be tested ### This DOES cause problems, i.e. intersecting the numbers 2 and 5 if DotVecs(elA.f.normal,cf.f.normal)>0: elA.state=cf.state=ES_COSAME cf.sidescomputed = True else: elA.state=cf.state=ES_COOPPO cf.sidescomputed = True else: d=DotVecs(elA.getENorm(),cf.f.normal) #print "!!!!!!!!!!!this face:",fA,"other:",cf if (d>-0.00001 and d < 0.00001): if showdbg or 1: print "e.cS(): BUG? Coplanar, but not?",d,closest,cf.getENorm(),elA.getENorm(),cf.f.normal,elA.f.normal elif d>0: elA.state=ES_OUTSIDE else: elA.state=ES_INSIDE #might as well to the other at the same time if cf.state == ES_UNKNOWN: d=DotVecs(cf.getENorm(),elA.f.normal) if (d>-0.00001 and d < 0.00001): if showdbg or 1: print "e.cS(): BUG? Coplanar, but not? (2)",d,closest elif d>0: cf.state=ES_OUTSIDE cf.sidescomputed = True else: cf.state=ES_INSIDE cf.sidescomputed = True else: if showdbg: print "e.cS(): Need to determine side recursively..." self.sidescomputed = 1 return if showdbg: print "e.cS(): Computed Sides!",self.f self.sidescomputed=1 return def addFacetLinks(e, f): if len(e.children): #if edge split, facet links belong to children for c in e.children: c.addFacetLinks(f) return #double check for ef in e.f: if ef.f == f: if showdbg: print "Inefficency: Facelink added for an edge that already has it" return ray = e.v[0].co - e.v[1].co #Yet another use for edge ray eflA0 = bEdgeFaceLink(e, f) eflA0.enorm = CrossVecs(f.normal, ray) eflA0.enorm.normalize() eflA1 = bEdgeFaceLink(e, f) eflA1.enorm = CrossVecs(f.normal, -ray) eflA1.enorm.normalize() eflA0.initLoop(eflA1) e.fixLoop(f, eflA0, eflA1) f.altered = True def addFacetLinksBetween(self, v0, v1, f): state = False if self.v[0] == v0 or self.v[0] == v1: state = True return self._addFacetLinksBetween(v0, v1, f, state) def _addFacetLinksBetween(self, v0, v1, f, state): if self.children: state = self.children[0]._addFacetLinksBetween(v0, v1, f, state) state = self.children[1]._addFacetLinksBetween(v0, v1, f, state) return state else: if state == True: self.addFacetLinks(f) if self.v[1] == v0 or self.v[1] == v1: state = not state return state class bMesh: def __init__(self,blenderObject,matrix = None): print "Adding mesh from: %s"%blenderObject.getName() mt = time() self.obj = blenderObject self.name = blenderObject.getName() self.mesh = m = blenderObject.getData(False, True) progress_lbl = "Importing %s"%(self.name) progress_max = float(len(m.verts)+len(m.edges)+len(m.faces)) progress = 0 DrawProgressBar(0, progress_lbl) self.verts = [] if (matrix): for v in m.verts: self.verts.append(bVert(v.co * matrix, v)) progress+=1 if ((progress & 511) == 0): DrawProgressBar(progress / progress_max, progress_lbl) else: for v in m.verts: self.verts.append(bVert(v.co, v)) progress+=1 if ((progress & 511) == 0): DrawProgressBar(progress / progress_max, progress_lbl) self.edges = [] for e in m.edges: self.edges.append(bEdge(self.verts[e.v1.index],self.verts[e.v2.index],e)) progress+=1 if ((progress & 511) == 0): DrawProgressBar(progress / progress_max, progress_lbl) self.facets = [] self.faces = [] for f in m.faces: if len(f.v) > 2: self.faces.append(bFace(self, f)) progress+=1 if ((progress & 511) == 0): DrawProgressBar(progress / progress_max, progress_lbl) print "\tAdd Time: ",time()-mt def getEdge(self, i1, i2): v1 = self.verts[i1] v2 = self.verts[i2] for e in v1.e: if e.v[0] == v2 or e.v[1] == v2: return e return None def constructFaces(self,otherObject,destMesh,inside,outside,cosame,coopposing,flip=1): global showdbg #if showdbg: print "m.cF(): Constructing Faces for ",self global cf_progress cf_progress = 0 if (len(self.faces)): DrawProgressBar(cf_progress / float(len(self.faces)), "Rebuild %s"%(self.name)) t=time() for f in self.faces: #print "r0" f.constructFace(True, destMesh,otherObject,inside,outside,cosame,coopposing,flip) #return #take care of any loose ends in a 2nd pass for f in self.faces: #print "r1" f.constructFace(False, destMesh,otherObject,inside,outside,cosame,coopposing,flip) print "Face fill time: ",time()-t def MergeEdge(eA, eB): global showdbg if eA == eB: return eA if showdbg: print "-=-=-=-=-=-= MERGE EDGES",eA.v[0].co, eA.v[1].co,eA,eB if len(eA.children) or len(eB.children): print "BUG! merging edges that have already been split" #ghf = ghjg for v in eB.v: v.e.remove(eB) if not eA in v.e: print "BUG? Edge not already linked to vert" v.e.append(eA) for f in eB.f: f.setEdge(eA) ok = 1 #This should maybe check that it isn't adding effectively the same facelink twice for efl in eA.f: if efl.f == f.f: print "BUG! Duplicate face entires in merged loop ##########################" print "fl:",f,efl print "e:",f.e,efl.e efl.printLoop() f.printLoop() #Make it so same item isn't in loop twice if efl.l2_next == f: print "A" efl.setL2Next(f.l2_next) elif f.l2_next == efl: print "B" f.l2_prev.setL2Next(efl) efl.printLoop() ok = 0 #raise Exception if ok: eA.f.append(f) return eA def MergeVert(vA, vB): global showdbg if vA==vB: return vA if showdbg: v = vA.co - vB.co print "+=+=+=+=+=+=+=+=+ I MERGEVERTS",vA,vB,v.length,vA.co,vB.co print "MV Edges: ",vA.e,vB.e for e in vB.e: if e in vA.e: print "BUG! Edge from old vert already assigned to new vert" if e.v[0] == vA or e.v[1] == vA: print "BUG? Merge shared edge?", e,e.v[0],e.v[0].co,e.v[1],e.v[1].co else: #print "Replace verts in ",e e.replaceVert(vB, vA) #if vB in e.v: # e.v[e.v.index(vB)]=vA vA.e.append(e) vB.merged = True #test if merge-edge neded for eA in vA.e: for eB in vA.e: if eA!=eB: shared = eA.getSharedVert(eB) ov1 = eA.getOtherVert(shared) ov2 = eB.getOtherVert(shared) if ov1==ov2: MergeEdge(eA, eB) else: #Does this really need to be done? v = ov1.co-ov2.co if v.length < 0.00001: if showdbg or 1: print "BUG? DOUBLE VERT at", ov1.co #MergeVert(ov1, ov2) def IntersectVertEdge(el, pt, vert = None): return el.e.cut(pt,vert) #Really vert x facet - must be convex + coplanar def IntersectVertFace(f,pt,vert = None): global vxf,svxe,svxf,sexe,sexf,vxe,exe #we can maybe do a bounds test here vxf+=1 el = f.el #edge loop elend = el in_edge = None while 1: if vert: vxe+=1 else: exe+=1 enorm = el.getENorm() dv = DotVecs(pt - el.e.v[0].co, enorm) if dv < -0.00001: return False if dv < 0.00002: #distance of vert from edge (Must be > than the prior test to make sure it doesn't count as inside a face where it should be outside) in_edge = el #note: if two edges in convex loop have an angle of near 180 degrees, it #may be necissary to keep multiple candidates for where the actual intersection was el = el.next if el==elend: break #vert lies in an edge if in_edge: if vert: svxe+=1 else: sexe+=1 return IntersectVertEdge(in_edge, pt, vert) #vert lies in the face if vert: svxf+=1 return vert else: sexf+=1 return True def CoplanarIntersectEdgeFace(e,f): #Both points of e lie in the plane of f global fexe fexe+=1 el = f.el #edge loop elend = el p0_inside = True p1_inside = True #Test points of test tedge vs. points of face edge cuts = [] while 1: enorm = el.getENorm() dv0 = DotVecs(e.v[0].co - el.e.v[0].co, enorm) dv1 = DotVecs(e.v[1].co - el.e.v[0].co, enorm) if abs(dv0) < 0.00005 and abs(dv1) < 0.00005: #edges share common segment #for each edge, test if any of its verts lie in the other edge if showdbg: print "Edges overlap each other", e, el.e bb0 = BoundingBox(el.e.v[0].co) bb0.expand(el.e.v[1].co) for v in e.getAllVerts(): bb1 = BoundingBox(v.co) if bb0.test(bb1, 0.00005): #print "A Cut edge at:",v.co nv = el.e.cut(v.co, v) #print " +-- ",nv ,v bb0 = BoundingBox(e.v[0].co) bb0.expand(e.v[1].co) for v in el.e.getAllVerts(): bb1 = BoundingBox(v.co) if bb0.test(bb1, 0.00005): #print "B Cut edge at:",v.co nv = e.cut(v.co, v) #print " +-- ",nv ,v return if (dv0 < 0): p0_inside = False if (dv1 < 0): p1_inside = False #If they cross the straddle the edge (or lie in edge?), find intersection of virtual edge-plane and edge if (dv0 < 0 and dv1 > 0) or (dv0 > 0 and dv1 < 0): #edges cross eray = e.v[1].co - e.v[0].co #again with the edge ray d = DotVecs(enorm, eray) n = DotVecs(enorm, el.e.v[0].co - e.v[0].co) #aka -1 * dv1 nd = n/d pt = e.v[0].co+nd * eray #is point with the range of the face's edge? bb0 = BoundingBox(el.e.v[0].co) bb0.expand(el.e.v[1].co) bb1 = BoundingBox(pt) if bb0.test(bb1, 0.00005): #tolerance here _must_ be less that the cut edge vert snap tolerant if showdbg: print "EDGE x EDGE at ",pt vert = e.cut(pt) vert = el.e.cut(pt, vert) if not vert in cuts: cuts.append(vert) #else: # print "**************** ExE fail @ ",pt el = el.next if el==elend: break if len(cuts) > 2: print "BUG! edge in face cut more than twice",e.v[0].co,e.v[1].co for v in cuts: print " \-- cut at ",v.co if len(cuts): f.markAltered() #cut twice - edges crosses completely through face if len(cuts) == 2: if showdbg: print "Edge ",e," crosses completely through face",f, cuts[0].co, cuts[1].co e.addFacetLinksBetween(cuts[0], cuts[1],f) #if both verts of the edge lie inside the face, add it to the face elif p0_inside and p1_inside: if showdbg: print "EDGE ",e," LIES COMPLETELY IN FACE" e.addFacetLinks(f) for ft in e.f: ft.f.markAltered() #cut once => one of the two end verts lie in the face: elif len(cuts) == 1: if p0_inside and e.v[0] != cuts[0]: #print "outside to inside",e e.addFacetLinksBetween(e.v[0], cuts[0],f) elif p1_inside and e.v[1] != cuts[0]: #print "outside to inside2",e e.addFacetLinksBetween(cuts[0], e.v[1],f) else: if cuts[0] not in e.getAllVerts(): print "BUG: Edge cuts face's edge once, but no other point lines in face" print " |-- edge: ",e.v[0].co, e.v[1].co print " \-- cut: ",cuts[0].co return cuts[0] #I'm not 100% sure this should be returned return False def IntersectEdgeFace(e,f): global showdbg global exf, sexf,fexe if showdbg: print "Edge ",e, " vs Facet",f print " +-- E: ",e.v[0].co, e.v[1].co, e.cutpoint el = f.el elend = el while True: print " +-- Fs: ",el,el.e, el.getNextVert().co el = el.next if el == elend: break exf+=1 d0 = DotVecs(e.v[0].co - f.e[0].v[0].co, f.normal) d1 = DotVecs(e.v[1].co - f.e[0].v[0].co, f.normal) i = 0 if abs(d0) < 0.00001 and abs(d1) < 0.00001: #both points of the edge lie in this face, so need to do ExE testing return CoplanarIntersectEdgeFace(e,f) elif abs(d0) < 0.00001: #distance of vert from surface iv = IntersectVertFace(f,e.v[0].co,e.v[0]) if iv: i = 1 elif abs(d1) < 0.00001: iv = IntersectVertFace(f,e.v[1].co,e.v[1]) if iv: i+= 1 #Edge lies in face -- actually we won't get here... if i == 2: #Need to add new EdgeFace Links e.addFacetLinks(f) return None #Edge touches faces elif i == 1: sexf += 1 return iv #Are the verts of either side of the face (different signs) elif i == 0 and d0 * d1 < 0: #print "maybe?" er = e.v[1].co - e.v[0].co #edge ray - maybe precomputed d = DotVecs(f.normal, er) if abs(d) > 0.000000000001: #avoid divide by zero, and unusably large results nd = -d0/d if (nd > -0.00000001 and nd < 1.0000001): #Note: relative to length of edge, maybe should be absolute length p = e.v[0].co + er * nd pt = IntersectVertFace(f,p) if pt == True: np = e.cut(p) if showdbg: print "Cut Edge new",p print "\- new point: ",np xpt.append([0,p]) return np elif pt: if showdbg: print "Cut edge with ",pt,"at",p e.cut(p,pt) return pt def DumbN2FullIntersect(meshA, meshB): global showdbg global traversalCrumb global fxf,ffxf if showdbg: print "Intersect",meshA,meshB for fA in meshA.faces: traversalCrumb+=1 fATrav = traversalCrumb for fB in meshB.faces: traversalCrumb+=1 fBTrav = traversalCrumb fxf+=1 #Bound check if fA.bb.test(fB.bb): ffxf+=1 for ftA in fA.facets: for ftB in fB.facets: #facet vs facet xpoints = [] for e in ftB.e: xA = IntersectEdgeFace(e,ftA) if xA and not xA in xpoints: #print "isectA @ ",xA xpoints.append(xA) #potentially, if two points have already been found, we can skip this loop for e in ftA.e: xB = IntersectEdgeFace(e,ftB) if xB and not xB in xpoints: #print "isectB @ ",xB xpoints.append(xB) #exactly two points => new edge if len(xpoints) == 2: #Does an edge exist already? found_edge = False for e in xpoints[0].e: if e.getOtherVert(xpoints[0]) == xpoints[1]: found_edge = True #Add edge links if not there already found_fa = None found_fb = None for el in e.f: if el.f == ftA: found_fa = el elif el.f == ftA: found_fb = el if not found_fa: e.addFacetLinks(ftA) if not found_fb: e.addFacetLinks(ftB) if not found_edge: if showdbg: xlen = xpoints[0].co - xpoints[1].co print "Make edge", xpoints[0].co, xpoints[1].co,xlen.length e = bEdge(xpoints[0], xpoints[1]) ftA.markAltered() ftB.markAltered() e.addFacetLinks(ftA) e.addFacetLinks(ftB) #one xpoint means faces touch, but don't intersect #over two and something weird happened elif len(xpoints) > 2: print "BUG:s Weird Facet x Facet" for pt in xpoints: print "\-- X@ ",pt,pt.co ################################################################### # Base functions direction the operation # ################################################################### def CookieCutter(cookie,cutter,newobjname="BoolResult"): global showdbg global makenewobj x=Blender.Draw.PupMenu("Faces to keep%t|Keep all|Inside only|Outside Only") if x<1: return bCookie=bMesh(cookie) bCutter=bMesh(cutter,cutter.getMatrix() * cookie.getInverseMatrix()) DumbN2FullIntersect(bCookie,bCutter) nm = Mesh.New(newobjname) 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) xptdraw(nm) nm.mode=bCookie.mesh.mode newobj=Blender.Scene.GetCurrent().objects.new(nm, newobjname) newobj.layers = cookie.layers newobj.setMatrix(cookie.getMatrix()) cookie.select(1) cutter.select(1) newobj.select(0) return def Difference(cookie,cutter,newobjname="BoolResult"): global showdbg global makenewobj bCookie=bMesh(cookie) bCutter=bMesh(cutter,cutter.getMatrix() * cookie.getInverseMatrix()) DumbN2FullIntersect(bCookie,bCutter) nm = Mesh.New(newobjname) bCutter.constructFaces(nm,bCookie,1,0,0,1,-1) bCookie.constructFaces(nm,bCutter,0,1,0,0,1) xptdraw(nm) nm.mode=bCookie.mesh.mode newobj=Blender.Scene.GetCurrent().objects.new(nm, newobjname) newobj.layers = cookie.layers newobj.setMatrix(cookie.getMatrix()) cookie.select(1) cutter.select(1) newobj.select(0) return def Intersection(cookie,cutter,newobjname="BoolResult"): global showdbg global makenewobj bCookie=bMesh(cookie) bCutter=bMesh(cutter,cutter.getMatrix() * cookie.getInverseMatrix()) DumbN2FullIntersect(bCookie,bCutter) nm = Mesh.New(newobjname) bCutter.constructFaces(nm,bCookie,1,0,1,0) bCookie.constructFaces(nm,bCutter,1,0,0,0) #bCookie.constructFaces(nm,bCutter,0,1,0,0) xptdraw(nm) nm.mode=bCookie.mesh.mode newobj=Blender.Scene.GetCurrent().objects.new(nm, newobjname) newobj.layers = cookie.layers newobj.setMatrix(cookie.getMatrix()) cookie.select(1) cutter.select(1) newobj.select(0) return def Union(cookie,cutter,newobjname="BoolResult"): global showdbg global makenewobj bCookie=bMesh(cookie) bCutter=bMesh(cutter,cutter.getMatrix() * cookie.getInverseMatrix()) DumbN2FullIntersect(bCookie,bCutter) nm = Mesh.New(newobjname) bCutter.constructFaces(nm,bCookie,0,1,1,0) bCookie.constructFaces(nm,bCutter,0,1,0,0) xptdraw(nm) nm.mode=bCookie.mesh.mode newobj=Blender.Scene.GetCurrent().objects.new(nm, newobjname) newobj.layers = cookie.layers newobj.setMatrix(cookie.getMatrix()) cookie.select(1) cutter.select(1) newobj.select(0) return def DoBool(): global showdbg #Check the sanity of the selection objsel=Object.GetSelected() meshes=0 for ob in objsel: if ob.getType() == 'Mesh': meshes += 1 if meshes!=2 : Blender.Draw.PupMenu("ERROR: Exactly two mesh objects must be selected") return x=Blender.Draw.PupMenu("MegaBool%t|1: Intersect|2: Union|3: Difference|4: Cookie Cutter") #print x ts=time() Blender.Window.WaitCursor(1) 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]) Blender.Window.WaitCursor(0) print "Script Execution Time: ",time()-ts #Try speed boost w/ psycho if usepsyco: if fullpsyco: psyco.full() else: 1 #Non coplanar quads #oA = Object.Get("SqSubCube") #oB = Object.Get("Cube") #Edge v face / virtual edge #oA = Object.Get("02-B.002") #oB = Object.Get("02-A.002") #Edge x Edge + vert in face #oA = Object.Get("02-B.001") #oB = Object.Get("02-A.001") #Edge + vert #oA = Object.Get("Cylinder.003") #oB = Object.Get("Cylinder.004") #vert x vert only #oA = Object.Get("02-B") #oB = Object.Get("02-A") #Edge & face intersections #oA = Object.Get("02-B.003") #oB = Object.Get("02-A.003") #Edges in face #oA = Object.Get("03-B") #oB = Object.Get("03-A") #Edges in face & verts fully in face w. tetrahedrons #oA = Object.Get("03-B.001") #oB = Object.Get("03-A.001") #Edges in face & edges in edges #oA = Object.Get("04-B") #oB = Object.Get("04-A") #Edges in face & edges in edges & cross edges #oA = Object.Get("05-B") #oB = Object.Get("05-A") #Weird Angles #oA = Object.Get("Cube.004") #oB = Object.Get("Cube") #Big Meshes #oA = Object.Get("BigSphereA") #oB = Object.Get("BigSphereB") #nonconvex face lies in face #oA = Object.Get("Cube.016") #oB = Object.Get("Cube.017") #nonconvex with full intersections #oA = Object.Get("Cube.010") #oB = Object.Get("Cube.008") #My "classic" test case used to demo problems with existing bools #oA = Object.Get("Cube.009") #oB = Object.Get("Cube.006") #Easy ExF only Tetrahedronds #oA = Object.Get("Pyramid-A") #oB = Object.Get("Pyramid-B") #Legan non-manifold meshes #oA = Object.Get("Cube.001") #oB = Object.Get("LegalNonManifold") #Selection #objsel = Object.GetSelected() #oA = objsel[0] #oB = objsel[1] #ts=time() #boA = bMesh(oA) #boB = bMesh(oB, oB.getMatrix()*oA.getInverseMatrix()) #DumbN2FullIntersect(boA,boB) #me = Mesh.New("mm") #boA.constructFaces(me, boB,1,0,1,0) #boB.constructFaces(me, boA,1,0,0,0) #xptdraw(me) #o = Blender.Scene.GetCurrent().objects.new(me, "mm") #o.setMatrix(oA.getMatrix()) #print "Script Execution Time: ",time()-ts DoBool() print "Done. Tests:\n\tFxF",fxf,"\t",ffxf,"\n\tExF",exf,"\t",fexf,"\t",sexf print "\tVxF",vxf,"\t--\t",svxf,"\n\tExE",exe,"\t",fexe,"\t",sexe,"\n\tVxE",vxe,"\t--\t",svxe,"\n\tVxV",vxv,"\t--\t",svxv if len(messages)>0 and 0: txt=Blender.Text.New("Boolean Messages") txt.clear() txt.write(messages) Blender.Window.DrawProgressBar(0.0, "Megabool Done") #Update traversal crumb on exe detection, to avoid multiple exe with the second face? #For handling vert merges, maybe old should always point to the child