From 15de493426279be4f7d1362b2553667d610a9445 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 18 Mar 2007 12:08:51 +0000 Subject: [PATCH] excellent mesh unolder script by Matthew Chadwick http://celeriac.net/unfolder/ --- release/scripts/mesh_unfolder.py | 1601 ++++++++++++++++++++++++++++++ 1 file changed, 1601 insertions(+) create mode 100644 release/scripts/mesh_unfolder.py diff --git a/release/scripts/mesh_unfolder.py b/release/scripts/mesh_unfolder.py new file mode 100644 index 00000000000..f725da31837 --- /dev/null +++ b/release/scripts/mesh_unfolder.py @@ -0,0 +1,1601 @@ +#!BPY +""" +Name: 'Unfold' +Blender: 243 +Group: 'Mesh' +Tip: 'Unfold meshes to create nets' +Version: v2.2.3 +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.3 07032007' +__url__ = ["http://celeriac.net/unfolder/", "blender", "blenderartist"] +__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 xrange(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 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)) + # need an epsilon closeTo() here + 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 xrange(self.face.nPoints()): + results.extend(something.haveSegment(self.segmentAt(i))) + return results + def compareSegments(self, something, segment): + results = [] + for i in xrange(self.face.nPoints()): + results.append(something.compareSegments([self.segmentAt(i), segment])) + return results + +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]) + + + +# 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,otherConstructor=None): + self.net = net + self.fold = fold + self.face = fold.srcFace + self.poly = Poly.fromBlenderFace(self.face) + self.generations = net.generations + self.growing = True + self.tooLong = False + self.parent = parent + self.grown = False + if not(otherConstructor): + self.edges = net.edgeIteratorClass(self) + def goodness(self): + return self.edges.goodness() + def compare(self, other): + if(self.goodness() > other.goodness()): + return +1 + else: + return -1 + def isGrowing(self): + return self.growing + def beGrowing(self): + self.growing = True + def grow(self): + self.tooLong = self.fold.ancestors()>self.generations + if(self.edges.hasNext() and self.growing): + edge = self.edges.next() + tface = self.net.facesAndEdges.takeAdjacentFace(self.face, edge) + if(tface!=None): + self.branch(tface, edge) + if(self.parent==None): + self.grow() + else: + self.grown = True + def isGrown(self): + return self.grown + def canGrow(self): + return (self.parent!=None and self.parent.grown) + def getNet(self): + return self.net + def getFold(self): + return self.fold + def getFace(self): + return self.face + 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) + 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.growing = 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.growing = 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 = RandomEdgeIterator + 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.noise = 0.0 + self.grownBranches = 0 + 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): + selectedFaces = [face for face in self.src.faces if (self.src.faceUV and face.flag & Mesh.FaceFlags.SELECT)] + if(self.avoidsOverlaps): + print "unfolding with overlap detection" + 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): + 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) + 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() + i = 0 + while(self.myFacesVisited 0): + if self.edgeIteratorClass==RandomEdgeIterator: + i = random.randint(0,len(self.branches)-1) + tree = self.branches[i] + if(tree.isGrown()): + self.branches.pop(i) + else: + tree.beGrowing() + if(tree.canGrow()): + tree.grow() + i = 0 + else: + i = (i + 1) % len(self.branches) + try: + for face in self.src.faces: + face.flag = 0 + for face in selectedFaces: + face.flag = Mesh.FaceFlags.SELECT + except: + pass + 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.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 xrange(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 xrange(3, 4*hits, 4)] + o = [folds[buffer[i]] for i in xrange(len(buffer))] + return self.getOverlapsBetween(fold, o) + def colourFace(self, face, cr): + for c in face.col: + c.r = int(cr[0]) + c.g = int(cr[1]) + c.b = int(cr[2]) + c.a = int(cr[3]) + 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: b1.compare(b2)) + def srcSize(self): + return len(self.src.faces) + def nBranches(self): + return len(self.branches) + 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().objects.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 xrange(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.verts: + v.co.x = tface.v[i].x + v.co.y = tface.v[i].y + v.co.z = tface.v[i].z + i+=1 + Window.Redraw(Window.Types.VIEW3D) + return None + def addFace(self, poly, originalFace=None): + originalLength = len(self.des.verts) + self.des.verts.extend([Vector(vv.x, vv.y, vv.z) for vv in poly.v]) + self.des.faces.extend([ range(originalLength, originalLength + poly.size()) ]) + newFace = self.des.faces[len(self.des.faces)-1] + newFace.uv = [vv for vv in poly.v] + if(originalFace!=None and self.src.vertexColors): + newFace.col = [c for c in originalFace.col] + if(self.feedback!=None): + pu = str(int(self.fractionUnfolded() * 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 newFace + def fractionUnfolded(self): + return float(self.myFacesVisited)/float(len(self.src.faces)) + def assignUV(self, face, uv): + face.uv = [Vector(v.x, v.y) for v in uv.v] + def unfoldAll(feedback=None): + objects = Blender.Object.Get() + for object in objects: + if(object.getType()=='Mesh' and not(object.getName().endswith("_net")) and len(object.getData(False, True).faces)>1): + net = Net.createNet(object, feedback) + net.searchForUnfolding() + svg = SVGExporter(net, object.getName()+".svg") + svg.export() + unfoldAll = staticmethod(unfoldAll) + def searchForUnfolding(self, limit=-1): + overlaps = 1 + attempts = 0 + while(overlaps > 0 or attempts=0 and (mesh.faces[mesh.activeFace].flag & Mesh.FaceFlags.SELECT)): + net.firstFaceIndex = mesh.activeFace + net.object = ob + net.feedback = feedback + return net + createNet = staticmethod(createNet) + def importNet(filename): + netName = filename.rstrip(".svg").replace("\\","/") + netName = netName[netName.rfind("/")+1:] + try: + netObject = Blender.Object.Get(netName) + except: + netObject = Blender.Object.New("Mesh", netName) + netObject.getData(mesh=1).name = netName + try: + Blender.Scene.GetCurrent().objects.link(netObject) + except: + pass + net = Net(None, netObject.getData(mesh=1)) + handler = NetHandler(net) + xml.sax.parse(filename, handler) + Window.Redraw(Window.Types.VIEW3D) + return net + importNet = staticmethod(importNet) + def getSourceMesh(self): + return self.src + + +class EdgeIterator: + def __init__(self, branch, otherConstructor=None): + self.branch = branch + self.bface = branch.getFace() + self.edge = branch.getFold().getEdge() + self.net = branch.getNet() + self.n = len(self.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( bface.v[i].co.copy(), bface.v[j].co.copy() ) + 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 xrange(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 & Mesh.EdgeFlags.SEAM) + def isInFGon(self): + return (self.bmEdge.flag & Mesh.EdgeFlags.FGON) + 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 + def goodness(self): + return self.gooodness + def setGoodness(self, g): + self.gooodness = g + def compare(self, other): + if(self.goodness() > other.goodness()): + return +1 + else: + return -1 + +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!=vv.x or vv.y!=vv.y or vv.z!=vv.z): # Nan check + 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) + # Todo, the 3 lines below in 1 LC + 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 size(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) + # Todo, the 3 lines below in 1 LC + 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() + p.v.extend([v.copy().resize4D() for v in vectors]) + return p + fromVectors = staticmethod(fromVectors) + def clone(self): + p = Poly() + p.v.extend(self.v) + 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.addClipPath() + 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 xrange(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 "grown." + 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() + atts = {} + atts["id"] = self.object.getName() + a = xml.sax.xmlreader.AttributesImpl(atts) + self.e.startElement("g", a) + #self.addUVImage() + self.addPolys() + self.addFoldLines() + #self.addCutLines() + self.e.endElement("g") + self.end() + def addClipPath(self): + atts = {} + atts["id"] = "netClip" + atts["clipPathUnits"] = "userSpaceOnUse" + atts["x"] = str(self.vxmin) + atts["y"] = str(self.vymin) + atts["width"] = "100%" + atts["height"] = "100%" + self.e.startElement("clipPath", atts) + self.addPolys() + self.e.endElement("clipPath") + def addUVImage(self): + image = Blender.Image.GetCurrent() + if image==None: + return + ifn = image.getFilename() + #ifn = self.filename.replace(".svg", ".jpg") + #image.setFilename(ifn) + #ifn = ifn[ifn.rfind("/")+1:] + #image.save() + atts = {} + atts["clip-path"] = "url(#netClip)" + atts["xlink:href"] = ifn + self.e.startElement("image", atts) + self.e.endElement("image") + def addPolys(self): + atts = {} + atts["id"] = "polys" + a = xml.sax.xmlreader.AttributesImpl(atts) + self.e.startElement("g", a) + for i in xrange(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 xrange( 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 xrange( 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 xrange(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.noise = Draw.Create(0.0) + self.shape = Draw.Create(0) + self.nOverlaps = 1==2 + self.iterators = [RandomEdgeIterator,Brightest,Curvature,EdgeIterator,OddEven,Largest] + self.iterator = RandomEdgeIterator + self.overlapsText = "*" + self.message = " " + def makePopupGUI(self): + useRandom = Draw.Create(0) + pub = [] + pub.append(("Search", self.search, "Search for non-overlapping net (maybe forever)")) + pub.append(("Random", useRandom, "Random style net")) + ok = True + while ok: + ok = Blender.Draw.PupBlock("Unfold", pub) + if ok: + if useRandom.val: + self.iterator = RandomEdgeIterator + else: + self.iterator = Curvature + self.unfold() + def makeStandardGUI(self): + 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 mesh_unfolder as 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 unfold(self): + anc = self.ancestors.val + n = 0.0 + s = True + self.nOverlaps = 0 + searchLimit = 10 + search = 1 + Draw.Redraw(1) + net = None + name = None + try: + self.say("Unfolding...") + Draw.Redraw(1) + while(s):# and search < searchLimit): + if(net!=None): + name = net.des.name + net = Net.unfoldSelected(self, name) + 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 + net.noise = self.noise.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() + search += 1 + 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) + def keyOrMouseEvent(self, evt, val): + if (evt == Draw.ESCKEY and not val): + Draw.Exit() + def buttonEvent(self, evt): + if (evt == 1): + self.unfold() + 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==714): + Net.unfoldAll(self) + 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, 9999, "depth of branching 0=diffuse") + #self.noise = Draw.Number("noise", 631, l.nx(), l.ny(), cw, ch, self.noise.val, 0.0, 1.0, "noisyness of branching") + #Draw.Button("UnfoldAll", 714, l.nx(), l.ny(), l.cw, l.ch, "Unfold all meshes and save their nets") + options = "order %t|random %x0|brightest %x1|curvature %x2|winding %x3| 1010 %x4|largest %x5" + 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.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 + +try: + sys.setrecursionlimit(10000) + gui = GUI() + gui.makeStandardGUI() + #gui.makePopupGUI() +except: + pass