#!BPY """ Name: 'Unfolder' Blender: 243 Group: 'Mesh' Tip: 'Unfold meshes to create nets' Version: v2.2.1 Author: Matthew Chadwick """ import Blender from Blender import * from Blender.Mathutils import * try: import sys import traceback import math import re from math import * import sys import random from decimal import * import xml.sax, xml.sax.handler, xml.sax.saxutils except: print "One of the Python modules required can't be found." print sys.exc_info()[1] traceback.print_exc(file=sys.stdout) __author__ = 'Matthew Chadwick' __version__ = '2.2.1 28022007' __url__ = ["http://celeriac.net/unfolder/", "blender", "elysiun"] __email__ = ["post at cele[remove this text]riac.net", "scripts"] __bpydoc__ = """\ Mesh Unfolder Unfolds the selected mesh onto a plane to form a net Not all meshes can be unfolded Meshes must be free of holes, isolated edges (not part of a face), twisted quads and other rubbish. Nice clean triangulated meshes unfold best This program is free software; you can distribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 or later, currently at http://www.gnu.org/copyleft/gpl.html The idea came while I was riding a bike. """ class FacesAndEdges: def __init__(self, mesh): self.nfaces = 0 # straight from the documentation self.edgeFaces = dict([(edge.key, []) for edge in mesh.edges]) for face in mesh.faces: face.sel = False for key in face.edge_keys: self.edgeFaces[key].append(face) def findTakenAdjacentFace(self, bface, edge): return self.findAdjacentFace(bface, edge) # find the first untaken (non-selected) adjacent face in the list of adjacent faces for the given edge def findAdjacentFace(self, bface, edge): faces = self.edgeFaces[edge.key()] for i in range(0, len(faces)): if faces[i] == bface: j = (i+1) % len(faces) while(faces[j]!=bface): if faces[j].sel == False: return faces[j] j = (j+1) % len(faces) return None def returnFace(self, face): face.sel = False self.nfaces-=1 def facesTaken(self): return self.nfaces def takeAdjacentFace(self, bface, edge): if (edge==None): return None face = self.findAdjacentFace(bface, edge) if(face!=None): face.sel = True self.nfaces+=1 return face def takeFace(self, bface): if(bface!=None): bface.sel= True self.nfaces+=1 def minz(mesh): return min([v.co.z for v in mesh.verts]) minz = staticmethod(minz) class Debug: def getDebugMesh(): mesh = NMesh.GetRaw("debug") if mesh==None: print "no debug mesh, creating one" mesh = NMesh.New() NMesh.PutRaw(mesh, "debug") return mesh getDebugMesh = staticmethod(getDebugMesh) def clearDebugMesh(): dm = Debug.getDebugMesh() dm.verts = [] dm.faces = [] dm.update() clearDebugMesh = staticmethod(clearDebugMesh) def plot(p): dm = Debug.getDebugMesh() nv = NMesh.Vert(p.x, p.y, p.z) dm.verts.append(nv) dm.update() Window.RedrawAll() plot = staticmethod(plot) def lineTo(p): dm = Debug.getDebugMesh() nv = NMesh.Vert(p.x, p.y, p.z) pv = dm.verts.pop() dm.verts.append(nv) dm.addEdge(pv, nv) dm.update() Window.RedrawAll() lineTo = staticmethod(lineTo) def drawPoly(poly): v = [] dm = Debug.getDebugMesh() for i in range(0, len(poly.v)): nv = NMesh.Vert(poly.v[i].x, poly.v[i].y, 0.0) v.append(nv) dm.verts.append(nv) f = NMesh.Face(v) dm.addFace(f) dm.update() Window.RedrawAll() drawPoly = staticmethod(drawPoly) class IntersectionResult: def __init__(self, rn, rd, v=None): self.v = v self.rd = rd self.rn = rn def intersected(self): return not(not(self.v)) def isParallel(self): return (self.rd==0) def isColinear(self): return (self.rn==0) def intersection(self): return self.v # represents a line segment between two points [p1, p2]. the points are [x,y] class LineSegment: def __init__(self, p): self.p = p def intersects(self, s): rn = ((self.p[0].y-s.p[0].y)*(s.p[1].x-s.p[0].x)-(self.p[0].x-s.p[0].x)*(s.p[1].y-s.p[0].y)) rd = ((self.p[1].x-self.p[0].x)*(s.p[1].y-s.p[0].y)-(self.p[1].y-self.p[0].y)*(s.p[1].x-s.p[0].x)) if(rd<0.0000001 or rn==0.0): return IntersectionResult(rn,rd) r = rn/rd s = ((self.p[0].y-s.p[0].y)*(self.p[1].x-self.p[0].x)-(self.p[0].x-s.p[0].x)*(self.p[1].y-self.p[0].y)) / rd i = (0.0<=r and r<=1.0 and 0.0<=s and s<=1.0) if not(i): return None ix = self.p[0].x + r*(self.p[1].x - self.p[0].x) iy = self.p[0].y + r*(self.p[1].y - self.p[0].y) t = 0.0001 if ( abs(ix-self.p[0].x)>t and abs(iy-self.p[0].x)>t and abs(ix-self.p[1].x)>t and abs(iy-self.p[1].y)>t ): return IntersectionResult( rn, rd,Vector([ix,iy,0.0])) else: return None class LineSegments: def __init__(self, face): self.face = face def segmentAt(self, i): if(i>self.face.nPoints()-1): return None if(i==self.face.nPoints()-1): j = 0 else: j = i+1 return LineSegment([ self.face.v[i], self.face.v[j] ]) def iterateSegments(self, something): results = [] for i in range(self.face.nPoints()): results.extend(something.haveSegment(self.segmentAt(i))) return results def compareSegments(self, something, segment): results = [] for i in range(self.face.nPoints()): results.append(something.compareSegments([self.segmentAt(i), segment])) return results # this is a bit convoluted and very slow class FaceOverlapTest: def __init__(self, face1, face2): self.faces = [face1, face2] self.segments = [ LineSegments(self.faces[0]), LineSegments(self.faces[1]) ] def suspectsOverlap(self): tests = self.segments[0].iterateSegments(self) gi = 0 for i in tests: if( i!=None and i.intersected() ): gi+=1 return gi>0 def haveSegment(self, segment): return self.segments[1].compareSegments(self, segment) def compareSegments(self, segments): return segments[0].intersects(segments[1]) # the overlap test only works one way for some shapes def testOverlap(): m1 = Blender.Object.GetSelected()[0].data m2 = Blender.Object.GetSelected()[1].data f1 = Poly.fromBlenderFace(m1.faces[0]) f2 = Poly.fromBlenderFace(m2.faces[0]) ot1 = FaceOverlapTest(f1, f2) ot2 = FaceOverlapTest(f2, f1) return (ot1.suspectsOverlap() or ot2.suspectsOverlap()) def testContains(): m1 = Blender.Object.GetSelected()[0].data m2 = Blender.Object.GetSelected()[1].data f1 = Poly.fromBlenderFace(m1.faces[0]) f2 = Poly.fromBlenderFace(m2.faces[0]) return (f1.containsAnyOf(f2) or f2.containsAnyOf(f1)) class MyMaths: def min(a, b): if a<=b: return a else: return b min = staticmethod(min) def max(a, b): if a>=b: return a else: return b max = staticmethod(max) # A fold class Fold: ids = -1 def __init__(self, parent, refPoly, poly, edge, angle=None): Fold.ids+=1 self.id = Fold.ids self.refPoly = refPoly self.poly = poly self.srcFace = None self.desFace = None self.edge = edge self.foldedEdge = edge self.rm = None self.parent = parent self.tree = None if(refPoly!=None): self.refPolyNormal = refPoly.normal() self.polyNormal = poly.normal() if(angle==None): self.angle = self.calculateAngle() self.foldingPoly = poly.rotated(edge, self.angle) else: self.angle = angle self.foldingPoly = poly self.unfoldedEdge = self.edge self.unfoldedNormal = None self.animAngle = self.angle self.cr = None self.nancestors = None def reset(self): self.foldingPoly = self.poly.rotated(self.edge, self.dihedralAngle()) def getID(self): return self.id def getParent(self): return self.parent def ancestors(self): if(self.nancestors==None): self.nancestors = self.computeAncestors() return self.nancestors def computeAncestors(self): if(self.parent==None): return 0 else: return self.parent.ancestors()+1 def dihedralAngle(self): return self.angle def unfoldTo(self, f): self.animAngle = self.angle*f self.foldingPoly = self.poly.rotated(self.edge, self.animAngle) def calculateAngle(self): sangle = Mathutils.AngleBetweenVecs(self.refPolyNormal, self.polyNormal) if(sangle!=sangle): sangle=0.0 ncp = Mathutils.CrossVecs(self.refPolyNormal, self.polyNormal) dp = Mathutils.DotVecs(ncp, self.edge.vector) if(dp>0.0): return +sangle else: return -sangle def alignWithParent(self): pass def unfoldedNormal(self): return self.unfoldedNormal def getEdge(self): return self.edge def getFace(self): return self.poly def testFace(self): return Poly.fromVectors([self.edge.v1, self.edge.v2, Vector([0,0,0])]) def unfoldedFace(self): return self.foldingPoly def unfold(self): if(self.parent!=None): self.parent.foldFace(self) def foldFace(self, child): child.foldingPoly.rotate(self.edge, self.animAngle) if(self.parent!=None): self.parent.foldFace(child) class Cut(Fold): pass # Builds folds class Tree: def __init__(self, net, parent,fold, face): self.net = net self.fold = fold self.face = face self.poly = Poly.fromBlenderFace(self.face) self.edges = net.edgeIteratorClass(self.face, self.fold.edge, self.net) self.generations = net.generations self.canFold = True self.toolong = False self.parent = parent self.done = False def goodness(self): return self.edges.goodness() def grow(self): self.toolong = self.fold.ancestors()>self.generations while(self.edges.hasNext() and self.canFold): edge = self.edges.next() tface = self.net.facesAndEdges.takeAdjacentFace(self.face, edge) if(tface!=None): self.branch(tface, edge) self.done = True def canGrow(self): if(self.parent!=None): d = self.parent.done else: d = False return d def branch(self, tface, edge): fold = Fold(self.fold, self.poly, Poly.fromBlenderFace(tface), edge) fold.srcFace = tface self.net.myFacesVisited+=1 tree = Tree(self.net, self, fold, tface) fold.tree = tree fold.unfold() overlaps = self.net.checkOverlaps(fold) nc = len(overlaps) self.net.overlaps+=nc if(nc>0 and self.net.avoidsOverlaps): self.handleOverlap(fold, overlaps) else: self.addFace(fold) def handleOverlap(self, fold, overlaps): self.net.facesAndEdges.returnFace(fold.srcFace) self.net.myFacesVisited-=1 for cfold in overlaps: ttree = cfold.tree ttree.canFold = True ttree.grow() def addFace(self, fold): ff = fold.unfoldedFace() fold.desFace = self.net.addFace(ff, fold.srcFace) self.net.folds.append(fold) self.net.addBranch(fold.tree) fold.tree.canFold = not(self.toolong) if(self.net.diffuse==False): fold.tree.grow() # Nets class Net: def __init__(self, src, des): self.src = src self.des = des self.firstFace = None self.firstPoly = None self.refFold = None self.edgeIteratorClass = Curvature if(src!=None): self.srcFaces = src.faces self.facesAndEdges = FacesAndEdges(self.src) self.myFacesVisited = 0 self.facesAdded = 0 self.folds = [] self.cuts = [] self.branches = [] self.overlaps = 0 self.avoidsOverlaps = True self.frame = 1 self.ff = 180.0 self.firstFaceIndex = None self.trees = 0 self.foldIPO = None self.perFoldIPO = None self.IPOCurves = {} self.generations = 128 self.diffuse = True self.assignsUV = True self.animates = False self.showProgress = False self.feedback = None def setSelectedFaces(self, faces): self.srcFaces = faces self.facesAndEdges = FacesAndEdges(self.srcFaces) def setShowProgress(self, show): self.showProgress = show # this method really needs work def unfold(self): if(self.avoidsOverlaps): print "unfolding with overlap detection" Debug.clearDebugMesh() if(self.firstFaceIndex==None): self.firstFaceIndex = random.randint(0, len(self.src.faces)-1) else: print "Using user-selected seed face ", self.firstFaceIndex self.firstFace = self.src.faces[self.firstFaceIndex] z = FacesAndEdges.minz(self.src)-0.1 ff = Poly.fromBlenderFace(self.firstFace) if(len(ff.v)<3): ff.flag = NMesh.FaceFlags['SELECT'] raise Exception("This mesh contains an isolated edge - it must consist of only faces") testFace = Poly.fromVectors( [ Vector([0.0,0.0,0.0]), Vector([0.0,1.0,0.0]), Vector([1.0,1.0,0.0]) ] ) # hmmm u=0 v=1 w=2 if ff.v[u].x==ff.v[u+1].x and ff.v[u].y==ff.v[u+1].y: u=1 v=2 w=0 xyFace = Poly.fromList( [ [ff.v[u].x,ff.v[u].y, z] , [ff.v[v].x,ff.v[v].y, z] , [ff.v[w].x+0.1,ff.v[w].y+0.1, z] ] ) refFace = Poly.fromVectors([ ff.v[u], ff.v[v], xyFace.v[1], xyFace.v[0] ] ) xyFold = Fold(None, xyFace, refFace, Edge(xyFace.v[0], xyFace.v[1] )) self.refFold = Fold(xyFold, refFace, ff, Edge(refFace.v[0], refFace.v[1] )) self.refFold.srcFace = self.firstFace trunk = Tree(self, None, self.refFold, self.firstFace) trunk.generations = self.generations self.firstPoly = ff self.facesAndEdges.takeFace(self.firstFace) self.myFacesVisited+=1 self.refFold.unfold() # All of his geese are swans self.refFold.tree = trunk self.refFold.desFace = self.addFace(self.refFold.unfoldedFace(), self.refFold.srcFace) self.folds.append(self.refFold) trunk.grow() while(self.myFacesVisited0): if self.edgeIteratorClass==RandomEdgeIterator: i = random.randint(0,len(self.branches)-1) else: i = 0 tree = self.branches[i] if(tree.done): self.branches.pop(i) else: tree.canFold = (3==3) if(tree.canGrow()): tree.grow() self.src.update() Window.RedrawAll() def assignUVs(self): for fold in self.folds: self.assignUV(fold.srcFace, fold.unfoldedFace()) print " assigned uv to ", len(self.folds), len(self.src.faces) #self.src.hasFaceUV(1) self.src.update() def checkOverlaps(self, fold): #return self.getOverlapsBetween(fold, self.folds) return self.getOverlapsBetweenGL(fold, self.folds) def getOverlapsBetween(self, fold, folds): if(fold.parent==None): return [] mf = fold.unfoldedFace() c = [] for afold in folds: mdf = afold.unfoldedFace() if(afold!=fold): it1 = FaceOverlapTest(mf, mdf) it2 = FaceOverlapTest(mdf, mf) overlap = (it1.suspectsOverlap() or it2.suspectsOverlap()) inside = ( mdf.containsAnyOf(mf) or mf.containsAnyOf(mdf) ) if( overlap or inside or mdf.overlays(mf)): c.append(afold) return c def getOverlapsBetweenGL(self, fold, folds): b = fold.unfoldedFace().bounds() polys = len(folds)*4+16 # the buffer is nhits, mindepth, maxdepth, name buffer = BGL.Buffer(BGL.GL_INT, polys) BGL.glSelectBuffer(polys, buffer) BGL.glRenderMode(BGL.GL_SELECT) BGL.glInitNames() BGL.glPushName(0) BGL.glPushMatrix() BGL.glMatrixMode(BGL.GL_PROJECTION) BGL.glLoadIdentity() BGL.glOrtho(b[0].x, b[1].x, b[1].y, b[0].y, 0.0, 10.0) #clip = BGL.Buffer(BGL.GL_FLOAT, 4) #clip.list = [0,0,0,0] #BGL.glClipPlane(BGL.GL_CLIP_PLANE1, clip) # could use clipping planes here too BGL.glMatrixMode(BGL.GL_MODELVIEW) BGL.glLoadIdentity() bx = (b[1].x - b[0].x) by = (b[1].y - b[0].y) cx = bx / 2.0 cy = by / 2.0 for f in range(0, len(folds)): afold = folds[f] if(fold!=afold): BGL.glLoadName(f) BGL.glBegin(BGL.GL_LINE_LOOP) for v in afold.unfoldedFace().v: BGL.glVertex2f(v.x, v.y) BGL.glEnd() BGL.glPopMatrix() BGL.glFlush() hits = BGL.glRenderMode(BGL.GL_RENDER) buffer = [buffer[i] for i in range(3, 4*hits, 4)] o = [folds[buffer[i]] for i in range(0, len(buffer))] return self.getOverlapsBetween(fold, o) def colourFace(self, face, cr): face.col = [] c = NMesh.Col(cr[0], cr[1], cr[2], cr[3]) for v in face: face.col.append(c) self.src.hasVertexColours(1) self.src.update() def setAvoidsOverlaps(self, avoids): self.avoidsOverlaps = avoids def addBranch(self, branch): self.branches.append(branch) if self.edgeIteratorClass!=RandomEdgeIterator: self.branches.sort(lambda b1, b2: int(round(b2.goodness() - b1.goodness()))) def srcSize(self): return len(self.src.faces) def facesCreated(self): return len(self.des.faces) def facesVisited(self): return self.myFacesVisited def getOverlaps(self): return self.overlaps def sortOutIPOSource(self): print "Sorting out IPO" if self.foldIPO!=None: return o = None try: o = Blender.Object.Get("FoldRate") except: o = Blender.Object.New("Empty", "FoldRate") Blender.Scene.GetCurrent().link(o) if(o.getIpo()==None): ipo = Blender.Ipo.New("Object", "FoldRateIPO") z = ipo.addCurve("RotZ") print " added RotZ IPO curve" z.addBezier((1,0)) # again, why is this 10x out ? z.addBezier((180, self.ff/10.0)) z.addBezier((361, 0.0)) o.setIpo(ipo) z.recalc() z.setInterpolation("Bezier") z.setExtrapolation("Cyclic") self.setIPOSource(o) print " added IPO source" def setIPOSource(self, object): try: self.foldIPO = object for i in range(self.foldIPO.getIpo().getNcurves()): self.IPOCurves[self.foldIPO.getIpo().getCurves()[i].getName()] = i print " added ", self.foldIPO.getIpo().getCurves()[i].getName() except: print "Problem setting IPO object" print sys.exc_info()[1] traceback.print_exc(file=sys.stdout) def setFoldFactor(self, ff): self.ff = ff def sayTree(self): for fold in self.folds: if(fold.getParent()!=None): print fold.getID(), fold.dihedralAngle(), fold.getParent().getID() def report(self): p = int(float(self.myFacesVisited)/float(len(self.src.faces)) * 100) print str(p) + "% unfolded" print "faces created:", self.facesCreated() print "faces visited:", self.facesVisited() print "originalfaces:", len(self.src.faces) n=0 if(self.avoidsOverlaps): print "net avoided at least ", self.getOverlaps(), " overlaps ", n = len(self.src.faces) - self.facesCreated() if(n>0): print "but was unable to avoid ", n, " overlaps. Incomplete net." else: print "- A complete net." else: print "net has at least ", self.getOverlaps(), " collision(s)" return n # fold all my folds to a fraction of their total fold angle def unfoldToCurrentFrame(self): self.unfoldTo(Blender.Scene.GetCurrent().getRenderingContext().currentFrame()) def unfoldTo(self, frame): frames = Blender.Scene.GetCurrent().getRenderingContext().endFrame() if(self.foldIPO!=None and self.foldIPO.getIpo()!=None): f = self.foldIPO.getIpo().EvaluateCurveOn(self.IPOCurves["RotZ"],frame) # err, this number seems to be 10x less than it ought to be fff = 1.0 - (f*10.0 / self.ff) else: fff = 1.0-((frame)/(frames*1.0)) for fold in self.folds: fold.unfoldTo(fff) for fold in self.folds: fold.unfold() tface = fold.unfoldedFace() bface = fold.desFace i = 0 for v in bface: v.co[0] = tface.v[i].x v.co[1] = tface.v[i].y v.co[2] = tface.v[i].z i+=1 self.des.update() Window.Redraw(Window.Types.VIEW3D) return None def addFace(self, tface, oface=None): nface = NMesh.Face() nface.uv = [] for v in tface.v: nv = NMesh.Vert(v.x, v.y, v.z) self.des.verts.append(nv) nface.append(nv) nface.uv.append((v.x, v.y)) if(oface!=None and self.src.vertexColors): for i in range(0, len(oface.col)-1): col = oface.col[i] nface.col.append(NMesh.Col()) nface.col[i].r = col.r nface.col[i].g = col.g nface.col[i].b = col.b nface.col[i].a = col.a nface.flag = oface.flag self.des.faces.append(nface) self.des.hasVertexColours(1) self.des.update() if(self.feedback!=None): p = float(self.myFacesVisited)/float(len(self.src.faces)) pu = str(int(p * 100))+"% unfolded" howMuchDone = str(self.myFacesVisited)+" of "+str(len(self.src.faces))+" "+pu self.feedback.say(howMuchDone) #Window.DrawProgressBar (p, pu) if(self.showProgress): Window.Redraw(Window.Types.VIEW3D) return nface def assignUV(self, face, uv): face.uv = [Vector(v.x, v.y) for v in uv.v] def removeFace(self, face): try: self.des.faces.remove(face) self.des.update() Window.RedrawAll() except: pass return None def unfoldSelectedFaces(feedback=None): object = Blender.Object.GetSelected()[0] mesh = NMesh.GetRaw(object.getName()) ff = random.randint(0, len(mesh.faces)-1) bface = mesh.faces[ff] newName = object.name+"_net" newObject = None newMesh = NMesh.New() NMesh.PutRaw(newMesh, newName) try: newMesh.materials = mesh.materials except: print "Problem setting materials here" net = Net(bface, mesh, newMesh) try: faces = mesh.getSelectedFaces() print "Unfolding selected faces ", faces if(faces!=None): net.setSelectedFaces(faces) except: traceback.print_exc(file=sys.stdout) pass return net unfoldSelectedFaces = staticmethod(unfoldSelectedFaces) def unfoldSelected(feedback=None): return Net.createNet(Blender.Object.GetSelected()[0], feedback) unfoldSelected = staticmethod(unfoldSelected) def clone(self): return Net.createNet(self.object, self.feedback) def createNet(object, feedback=None): mesh = Mesh.Get(object.data.name) nmesh = NMesh.GetRaw(object.data.name) newName = object.name+"_net" newObject = None newMesh = NMesh.New() newMesh.name = newName newObject = NMesh.PutRaw(newMesh, newName) if(newObject!=None): newObject.setName(newName) try: newMesh.materials = mesh.materials newMesh.hasVertexColours(1) except: print "Problem setting materials here" selected = nmesh.getSelectedFaces(1) net = Net(mesh, newMesh) if(selected!=None and len(selected)>0): net.firstFaceIndex = selected[0] net.object = object net.feedback = feedback return net createNet = staticmethod(createNet) def importNet(filename): newName = filename.rstrip(".svg").replace("\\","/") newName=newName[newName.rfind("/")+1:] newObject = None newMesh = NMesh.New() newMesh.name = newName newObject = NMesh.PutRaw(newMesh, newName) if(newObject!=None): newObject.setName(newName) newMesh.hasVertexColours(1) net = Net(None, newMesh) handler = NetHandler(net) xml.sax.parse(filename, handler) return net importNet = staticmethod(importNet) def getSourceMesh(self): return self.src class EdgeIterator: def __init__(self, bface, edge, net, otherConstructor=None): self.bface = bface self.edge = edge self.net = net self.n = len(bface) self.edges = [] self.i = 0 self.gooodness = 0 self.createEdges() self.computeGoodness() if(otherConstructor==None): self.sequenceEdges() def createEdges(self): edge = None e = Edge.edgesOfBlenderFace(self.net.getSourceMesh(), self.bface) for edge in e: if not(edge.isBlenderSeam() and edge!=self.edge): self.edges.append(edge) def sequenceEdges(self): pass def next(self): edge = self.edges[self.i] self.i+=1 return edge def size(self): return len(self.edges) def reset(self): self.i = 0 def hasNext(self): return (self.ilen(bface)-1): return None if(i==len(bface)-1): j = 0 else: j = i+1 edge = Edge( Vector([x for x in bface.v[i].co]), Vector([ x for x in bface.v[j].co])) edge.bEdge = mesh.findEdge(bface.v[i], bface.v[j]) edge.idx = i return edge fromBlenderFace=staticmethod(fromBlenderFace) def edgesOfBlenderFace(mesh, bmFace): edges = [mesh.edges[mesh.findEdges(edge[0], edge[1])] for edge in bmFace.edge_keys] v = bmFace.verts e = [] vi = v[0] i=0 for j in range(1, len(bmFace)+1): vj = v[j%len(bmFace)] for ee in edges: if((ee.v1.index==vi.index and ee.v2.index==vj.index) or (ee.v2.index==vi.index and ee.v1.index==vj.index)): e.append(Edge(vi.co, vj.co, ee, i)) i+=1 vi = vj return e edgesOfBlenderFace=staticmethod(edgesOfBlenderFace) def isBlenderSeam(self): # Better and flutter must and man can beam. Now think of seams. return (self.bmEdge.flag & NMesh.EdgeFlags.SEAM) def mapTo(self, poly): if(self.idx==len(poly.v)-1): j = 0 else: j = self.idx+1 return Edge(poly.v[self.idx], poly.v[j]) def isDegenerate(self): return self.vector.length==0 def vertices(s): return [ [s.v1.x, s.v1.y, s.v1.z], [s.v2.x, s.v2.y,s.v2.z] ] def key(self): return self.bmEdge.key class Poly: ids = -1 def __init__(self): Poly.ids+=1 self.v = [] self.id = Poly.ids self.boundz = None def getID(self): return self.id def normal(self): a =self.v[0] b=self.v[1] c=self.v[2] p = b-a p.resize3D() q = a-c q.resize3D() return CrossVecs(p,q) def isBad(self): badness = 0 for vv in self.v: if(vv.x==Decimal('NaN') or vv.y==Decimal('NaN') or vv.y==Decimal('NaN')): badness+=1 return (badness>0) def midpoint(self): x=y=z = 0.0 n = 0 for vv in self.v: x+=vv.x y+=vv.y z+=vv.z n+=1 return [ x/n, y/n, z/n ] def centerAtOrigin(self): mp = self.midpoint() mp = -mp toOrigin = TranslationMatrix(mp) self.v = [(vv * toOrigin) for vv in self.v] def move(self, tv): mv = TranslationMatrix(tv) self.v = [(vv * mv) for vv in self.v] def scale(self, s): mp = Vector(self.midpoint()) fromOrigin = TranslationMatrix(mp) mp = -mp toOrigin = TranslationMatrix(mp) sm = ScaleMatrix(s, 4) self.v = [(vv * toOrigin) for vv in self.v] self.v = [(sm * vv) for vv in self.v] self.v = [(vv * fromOrigin) for vv in self.v] def nPoints(self): return len(self.v) def rotated(self, axis, angle): p = self.clone() p.rotate(axis, angle) return p def rotate(self, axis, angle): rotation = RotationMatrix(angle, 4, "r", axis.vector) toOrigin = TranslationMatrix(axis.v1n) fromOrigin = TranslationMatrix(axis.v1) self.v = [(vv * toOrigin) for vv in self.v] self.v = [(rotation * vv) for vv in self.v] self.v = [(vv * fromOrigin) for vv in self.v] def moveAlong(self, vector, distance): t = TranslationMatrix(vector) s = ScaleMatrix(distance, 4) ts = t*s self.v = [(vv * ts) for vv in self.v] def bounds(self): if(self.boundz == None): vv = [vv for vv in self.v] vv.sort(key=lambda v: v.x) minx = vv[0].x maxx = vv[len(vv)-1].x vv.sort(key=lambda v: v.y) miny = vv[0].y maxy = vv[len(vv)-1].y self.boundz = [Vector(minx, miny, 0), Vector(maxx, maxy, 0)] return self.boundz def fromBlenderFace(bface): p = Poly() for vv in bface.v: vec = Vector([vv.co[0], vv.co[1], vv.co[2] , 1.0]) p.v.append(vec) return p fromBlenderFace = staticmethod(fromBlenderFace) def fromList(list): p = Poly() for vv in list: vec = Vector( [vvv for vvv in vv] ) vec.resize4D() p.v.append(vec) return p fromList = staticmethod(fromList) def fromVectors(vectors): p = Poly() for v in vectors: vec = Vector(v) vec.resize4D() p.v.append(vec) return p fromVectors = staticmethod(fromVectors) def clone(self): p = Poly() for vv in self.v: p.v.append(vv) return p def hasVertex(self, ttv): v = Mathutils.Vector(ttv) v.normalize() for tv in self.v: vv = Mathutils.Vector(tv) vv.normalize() t = 0.00001 if abs(vv.x-v.x)0): j=i-1 cv = self.v[i] nv = self.v[j] if ((((cv.y<=tp.y) and (tp.y") self.e.endElement("style") self.e.endElement("defs") self.addMeta() def addMeta(self): self.e.startElement("metadata", xml.sax.xmlreader.AttributesImpl({})) self.e.startElement("nets:net", xml.sax.xmlreader.AttributesImpl({})) for i in range(1, len(self.net.folds)): fold = self.net.folds[i] # AttributesNSImpl - documentation is rubbish. using this hack. atts = {} atts["nets:id"] = "fold"+str(fold.getID()) if(fold.parent!=None): atts["nets:parent"] = "fold"+str(fold.parent.getID()) else: atts["nets:parent"] = "null" atts["nets:da"] = str(fold.dihedralAngle()) if(fold.parent!=None): atts["nets:ofPoly"] = "poly"+str(fold.parent.foldingPoly.getID()) else: atts["nets:ofPoly"] = "" atts["nets:toPoly"] = "poly"+str(fold.foldingPoly.getID()) a = xml.sax.xmlreader.AttributesImpl(atts) self.e.startElement("nets:fold", a) self.e.endElement("nets:fold") self.e.endElement("nets:net") self.e.endElement("metadata") def end(self): self.e.endElement("svg") self.e.endDocument() print "done." def export(self): self.net.unfoldTo(1) bb = self.object.getBoundBox() self.vxmin = bb[0][0] self.vymin = bb[0][1] self.vxmax = bb[7][0] self.vymax = bb[7][1] self.start() self.addPolys() self.addFoldLines() #self.addCutLines() self.end() def addPolys(self): atts = {} atts["id"] = self.object.getName() a = xml.sax.xmlreader.AttributesImpl(atts) self.e.startElement("g", a) for i in range(0, len(self.net.folds)): self.addPoly(self.net.folds[i]) self.e.endElement("g") def addFoldLines(self): atts = {} atts["id"] = "foldLines" a = xml.sax.xmlreader.AttributesImpl(atts) self.e.startElement("g", a) for i in range( 1, len(self.net.folds)): self.addFoldLine(self.net.folds[i]) self.e.endElement("g") def addFoldLine(self, fold): edge = fold.edge.mapTo(fold.parent.foldingPoly) if fold.dihedralAngle()>0: foldType="valley" else: foldType="mountain" atts={} atts["x1"] = str(edge.v1.x) atts["y1"] = str(edge.v1.y) atts["x2"] = str(edge.v2.x) atts["y2"] = str(edge.v2.y) atts["id"] = "fold"+str(fold.getID()) atts["class"] = foldType a = xml.sax.xmlreader.AttributesImpl(atts) self.e.startElement("line", a) self.e.endElement("line") def addCutLines(self): atts = {} atts["id"] = "cutLines" a = xml.sax.xmlreader.AttributesImpl(atts) self.e.startElement("g", a) for i in range( 1, len(self.net.cuts)): self.addCutLine(self.net.cuts[i]) self.e.endElement("g") def addCutLine(self, cut): edge = cut.edge.mapTo(cut.parent.foldingPoly) if cut.dihedralAngle()>0: foldType="valley" else: foldType="mountain" atts={} atts["x1"] = str(edge.v1.x) atts["y1"] = str(edge.v1.y) atts["x2"] = str(edge.v2.x) atts["y2"] = str(edge.v2.y) atts["id"] = "cut"+str(cut.getID()) atts["class"] = foldType a = xml.sax.xmlreader.AttributesImpl(atts) self.e.startElement("line", a) self.e.endElement("line") def addPoly(self, fold): face = fold.foldingPoly atts = {} if fold.desFace.col: col = fold.desFace.col[0] rgb = "rgb("+str(col.r)+","+str(col.g)+","+str(col.b)+")" atts["fill"] = rgb atts["class"] = "poly" atts["id"] = "poly"+str(face.getID()) points = "" first = True for vv in face.v: if(not(first)): points+=',' first = (2==3) points+=str(vv[0]) points+=' ' points+=str(vv[1]) atts["points"] = points a = xml.sax.xmlreader.AttributesImpl(atts) self.e.startElement("polygon", a) self.e.endElement("polygon") def fileSelected(filename): try: net = Registry.GetKey('unfolder')['net'] exporter = SVGExporter(net, filename) exporter.export() except: print "Problem exporting SVG" traceback.print_exc(file=sys.stdout) fileSelected = staticmethod(fileSelected) class NetHandler(xml.sax.handler.ContentHandler): def __init__(self, net): self.net = net self.first = (41==41) self.currentElement = None self.chars = None self.currentAction = None self.foldsPending = {} self.polys = {} self.actions = {} self.actions["nets:fold"] = self.foldInfo self.actions["line"] = self.cutOrFold self.actions["polygon"] = self.createPoly def setDocumentLocator(self, locator): pass def startDocument(self): pass def endDocument(self): for fold in self.foldsPending.values(): face = self.net.addFace(fold.unfoldedFace()) fold.desFace = face self.net.folds.append(fold) self.net.addFace(self.first) self.foldsPending = None self.polys = None def startPrefixMapping(self, prefix, uri): pass def endPrefixMapping(self, prefix): pass def startElement(self, name, attributes): self.currentAction = None try: self.currentAction = self.actions[name] except: pass if(self.currentAction!=None): self.currentAction(attributes) def endElement(self, name): pass def startElementNS(self, name, qname, attrs): self.currentAction = self.actions[name] if(self.currentAction!=None): self.currentAction(attributes) def endElementNS(self, name, qname): pass def characters(self, content): pass def ignorableWhitespace(self): pass def processingInstruction(self, target, data): pass def skippedEntity(self, name): pass def foldInfo(self, atts): self.foldsPending[atts["nets:id"]] = atts def createPoly(self, atts): xy = re.split('[, ]' , atts["points"]) vectors = [] for i in range(0, len(xy)-1, 2): v = Vector([float(xy[i]), float(xy[i+1]), 0.0]) vectors.append(v) poly = Poly.fromVectors(vectors) if(self.first==True): self.first = poly self.polys[atts["id"]] = poly def cutOrFold(self, atts): fid = atts["id"] try: fi = self.foldsPending[fid] except: pass p1 = Vector([float(atts["x1"]), float(atts["y1"]), 0.0]) p2 = Vector([float(atts["x2"]), float(atts["y2"]), 0.0]) edge = Edge(p1, p2) parent = None ofPoly = None toPoly = None try: parent = self.foldsPending[fi["nets:parent"]] except: pass try: ofPoly = self.polys[fi["nets:ofPoly"]] except: pass try: toPoly = self.polys[fi["nets:toPoly"]] except: pass fold = Fold(parent, ofPoly , toPoly, edge, float(fi["nets:da"])) self.foldsPending[fid] = fold def fileSelected(filename): try: net = Net.importNet(filename) try: Registry.GetKey('unfolder')['net'] = net except: Registry.SetKey('unfolder', {}) Registry.GetKey('unfolder')['net'] = net Registry.GetKey('unfolder')['lastpath'] = filename except: print "Problem importing SVG" traceback.print_exc(file=sys.stdout) fileSelected = staticmethod(fileSelected) #____________Blender GUI__________________ class GUI: def __init__(self): self.overlaps = Draw.Create(0) self.ani = Draw.Create(0) self.selectedFaces =0 self.search = Draw.Create(0) self.diffuse = True self.ancestors = Draw.Create(0) self.shape = Draw.Create(0) self.nOverlaps = 1==2 self.iterators = [RandomEdgeIterator,Brightest,Curvature,EdgeIterator] self.iterator = RandomEdgeIterator self.overlapsText = "*" self.message = " " Draw.Register(self.draw, self.keyOrMouseEvent, self.buttonEvent) def installScriptLink(self): print "Adding script link for animation" s = Blender.Scene.GetCurrent().getScriptLinks("FrameChanged") if(s!=None and s.count("frameChanged.py")>0): return try: script = Blender.Text.Get("frameChanged.py") except: script = Blender.Text.New("frameChanged.py") script.write("import Blender\n") script.write("import Unfolder\n") script.write("u = Blender.Registry.GetKey('unfolder')\n") script.write("if u!=None:\n") script.write("\tn = u['net']\n") script.write("\tif(n!=None and n.animates):\n") script.write("\t\tn.unfoldToCurrentFrame()\n") Blender.Scene.GetCurrent().addScriptLink("frameChanged.py", "FrameChanged") def keyOrMouseEvent(self, evt, val): if (evt == Draw.ESCKEY and not val): Draw.Exit() def buttonEvent(self, evt): global mynet if (evt == 1): anc = self.ancestors.val n = 0.0 s = True self.nOverlaps = 0 Draw.Redraw(1) try: self.say("Unfolding...") Draw.Redraw(1) net = Net.unfoldSelected(self) while(s): net.setAvoidsOverlaps(not(self.overlaps.val)) print print "Unfolding selected object" net.edgeIteratorClass = self.iterator print "Using ", net.edgeIteratorClass net.animates = self.ani.val self.diffuse = (self.ancestors.val==0) net.diffuse = self.diffuse net.generations = self.ancestors.val print "even:", net.diffuse, " depth:", net.generations net.unfold() n = net.report() t = "." if(n<1.0): t = "Overlaps>="+str(n) else: t = "A complete net." self.nOverlaps = (n>=1) if(self.nOverlaps): self.say(self.message+" - unfolding failed - try again ") elif(not(self.overlaps.val)): self.say("Success. Complete net - no overlaps ") else: self.say("Unfolding complete") self.ancestors.val = anc s = (self.search.val and n>=1.0) dict = Registry.GetKey('unfolder') if(not(dict)): dict = {} dict['net'] = net Registry.SetKey('unfolder', dict) if(s): net = net.clone() except(IndexError): self.say("Please select an object to unfold") except: self.say("Problem unfolding selected object - see console for details") print "Problem unfolding selected object:" print sys.exc_info()[1] traceback.print_exc(file=sys.stdout) if(self.ani): if Registry.GetKey('unfolder')==None: print "no net!" return Registry.GetKey('unfolder')['net'].sortOutIPOSource() self.installScriptLink() Draw.Redraw(1) if (evt == 5): try: Registry.GetKey('unfolder')['net'].setAvoidsOverlaps(self.overlaps.val) except: pass if (evt == 2): print "Trying to set IPO curve" try: s = Blender.Object.GetSelected() if(s!=None): Registry.GetKey('unfolder')['net'].setIPOSource( s[0] ) print "Set IPO curve" else: print "Please select an object to use the IPO of" except: print "Problem setting IPO source" Draw.Redraw(1) if (evt == 6): Draw.Exit() if (evt == 7): try: if (Registry.GetKey('unfolder')['net']!=None): Registry.GetKey('unfolder')['net'].animates = self.ani.val if(self.ani): Registry.GetKey('unfolder')['net'].sortOutIPOSource() self.installScriptLink() except: print sys.exc_info()[1] traceback.print_exc(file=sys.stdout) Draw.Redraw(1) if (evt == 19): pass if (evt == 87): try: if (Registry.GetKey('unfolder')['net']!=None): Registry.GetKey('unfolder')['net'].assignUVs() self.say("Assigned UVs") except: print sys.exc_info()[1] traceback.print_exc(file=sys.stdout) Draw.Redraw(1) if(evt==91): if( testOverlap() == True): self.nOverlaps = 1 else: self.nOverlaps = 0 Draw.Redraw(1) if(evt==713): self.iterator = self.iterators[self.shape.val] Draw.Redraw(1) if(evt==92): if( testContains() == True): self.nOverlaps = 1 else: self.nOverlaps = 0 Draw.Redraw(1) if(evt==104): try: filename = "net.svg" s = Blender.Object.GetSelected() if(s!=None and len(s)>0): filename = s[0].getName()+".svg" else: if (Registry.GetKey('unfolder')['net']!=None): filename = Registry.GetKey('unfolder')['net'].des.name if(filename==None): filename="net.svg" else: filename=filename+".svg" Window.FileSelector(SVGExporter.fileSelected, "Select filename", filename) except: print "Problem exporting SVG" traceback.print_exc(file=sys.stdout) if(evt==107): try: Window.FileSelector(NetHandler.fileSelected, "Select file") except: print "Problem importing SVG" traceback.print_exc(file=sys.stdout) def say(self, m): self.message = m Draw.Redraw(1) Window.Redraw(Window.Types.SCRIPT) def draw(self): cw = 64 ch = 16 l = FlowLayout(32, cw, ch, 350, 64) l.y = 70 self.search = Draw.Toggle("search", 19, l.nx(), l.ny(), l.cw, l.ch, self.search.val, "Search for non-overlapping mesh (potentially indefinitely)") self.overlaps = Draw.Toggle("overlaps", 5, l.nx(), l.ny(), l.cw, l.ch, self.overlaps.val, "Allow overlaps / avoid overlaps - if off, will not place overlapping faces") self.ani = Draw.Toggle("ani", 7, l.nx(), l.ny(), l.cw, l.ch, self.ani.val, "Animate net") Draw.Button("uv", 87, l.nx(), l.ny(), l.cw, l.ch, "Assign net as UV to source mesh (overwriting existing UV)") Draw.Button("Unfold", 1, l.nx(), l.ny(), l.cw, l.ch, "Unfold selected mesh to net") Draw.Button("save", 104, l.nx(), l.ny(), l.cw, l.ch, "Save net as SVG") Draw.Button("load", 107, l.nx(), l.ny(), l.cw, l.ch, "Load net from SVG") # unfolding enthusiasts - try uncommenting this self.ancestors = Draw.Number("depth", 654, l.nx(), l.ny(), cw, ch, self.ancestors.val, 0, 999, "depth of branching 0=diffuse") options = "order %t|random %x0|brightest %x1|curvature %x2|winding %x3" self.shape = Draw.Menu(options, 713, l.nx(), l.ny(), cw, ch, self.shape.val, "shape of net") Draw.Button("exit", 6, l.nx(), l.ny(), l.cw, l.ch, "exit") BGL.glClearColor(0.5, 0.5, 0.5, 1) BGL.glColor3f(0.45,0.45,0.45) BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT) BGL.glRasterPos2i(32, 32) Draw.Text("Mesh Unfolder 2.2.1 by Matthew Chadwick (Celeriac) 2007", "tiny") BGL.glColor3f(0.3,0.3,0.3) l.newLine() BGL.glRasterPos2i(32, 100) Draw.Text(self.message) class FlowLayout: def __init__(self, margin, cw, ch, w, h): self.x = margin-cw-4 self.y = margin self.cw = cw self.ch = ch self.width = w self.height = h self.margin = margin def nx(self): self.x+=(self.cw+4) if(self.x>self.width): self.x = self.margin self.y-=self.ch+4 return self.x def ny(self): return self.y def newLine(self): self.y-=self.ch+self.margin self.x = self.margin sys.setrecursionlimit(10000) try: GUI() except: pass