Matthew Chadwick's update to the mesh unfolder, minor tidyups and stopped an error...
[blender.git] / release / scripts / mesh_unfolder.py
1 #!BPY
2 """
3 Name: 'Unfold'
4 Blender: 243
5 Group: 'Mesh'
6 Tip: 'Unfold meshes to create nets'
7 Version:  v2.2.4
8 Author: Matthew Chadwick
9 """
10 import Blender
11 from Blender import *
12 from Blender.Mathutils import *
13 try:
14         import sys
15         import traceback
16         import math
17         import re
18         from math import *
19         import sys
20         import random
21         from decimal import *
22         import xml.sax, xml.sax.handler, xml.sax.saxutils
23
24 except:
25         print "One of the Python modules required can't be found."
26         print sys.exc_info()[1]
27         traceback.print_exc(file=sys.stdout)
28         
29 __author__ = 'Matthew Chadwick'
30 __version__ = '2.2.4 24032007'
31 __url__ = ["http://celeriac.net/unfolder/", "blender", "blenderartist"]
32 __email__ = ["post at cele[remove this text]riac.net", "scripts"]
33 __bpydoc__ = """\
34
35 Mesh Unfolder
36
37 Unfolds the selected mesh onto a plane to form a net
38
39 Not all meshes can be unfolded
40
41 Meshes must be free of holes, 
42 isolated edges (not part of a face), twisted quads and other rubbish.
43 Nice clean triangulated meshes unfold best
44
45 This program is free software; you can distribute it and/or modify it under the terms
46 of the GNU General Public License as published by the Free Software Foundation; version 2
47 or later, currently at http://www.gnu.org/copyleft/gpl.html
48
49 The idea came while I was riding a bike.
50
51 """     
52
53 # Face lookup
54 class FacesAndEdges:
55         def __init__(self, mesh):
56                 self.nfaces = 0
57                 # straight from the documentation
58                 self.edgeFaces = dict([(edge.key, []) for edge in mesh.edges])
59                 for face in mesh.faces:
60                         face.sel = False
61                         for key in face.edge_keys:
62                                         self.edgeFaces[key].append(face)
63         def findTakenAdjacentFace(self, bface, edge):
64                 return self.findAdjacentFace(bface, edge)
65         # find the first untaken (non-selected) adjacent face in the list of adjacent faces for the given edge
66         def findAdjacentFace(self, bface, edge):
67                 faces = self.edgeFaces[edge.key()]
68                 for i in xrange(len(faces)):
69                         if faces[i] == bface:
70                                 j = (i+1) % len(faces)
71                                 while(faces[j]!=bface):
72                                         if faces[j].sel == False:
73                                                 return faces[j]
74                                         j = (j+1) % len(faces)
75                 return None
76         def returnFace(self, face):
77                 face.sel = False
78                 self.nfaces-=1
79         def facesTaken(self):
80                 return self.nfaces
81         def takeAdjacentFace(self, bface, edge):
82                 if (edge==None):
83                         return None
84                 face = self.findAdjacentFace(bface, edge)
85                 if(face!=None):
86                         face.sel = True
87                         self.nfaces+=1
88                         return face
89         def takeFace(self, bface):
90                 if(bface!=None):
91                         bface.sel= True
92                         self.nfaces+=1
93         
94         
95 class IntersectionResult:
96         def __init__(self, rn, rd, v=None):
97                 self.v = v
98                 self.rd = rd
99                 self.rn = rn
100         def intersected(self):
101                 return not(not(self.v))
102         def isParallel(self):
103                 return (self.rd==0)
104         def isColinear(self):
105                 return (self.rn==0)
106         def intersection(self):
107                 return self.v
108         
109 # represents a line segment between two points [p1, p2]. the points are [x,y]
110 class LineSegment:
111         def __init__(self, p):
112                 self.p = p
113         def intersects(self, s):
114                 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)) 
115                 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))
116                 # need an epsilon closeTo() here
117                 if(rd<0.0000001 or rn==0.0):
118                         return IntersectionResult(rn,rd)
119                 r = rn/rd
120                 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
121                 i = (0.0<=r and r<=1.0 and 0.0<=s and s<=1.0)
122                 if not(i):
123                         return None
124                 ix = self.p[0].x + r*(self.p[1].x - self.p[0].x)
125                 iy = self.p[0].y + r*(self.p[1].y - self.p[0].y)
126                 t = 0.0001
127                 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 ):
128                         return IntersectionResult( rn, rd,Vector([ix,iy,0.0]))
129                 else:
130                         return None
131                 
132 class LineSegments:
133         def __init__(self, face):
134                 self.face = face
135         def segmentAt(self, i):
136                 if(i>self.face.nPoints()-1):
137                         return None
138                 if(i==self.face.nPoints()-1):
139                         j = 0
140                 else:
141                         j = i+1
142                 return LineSegment([ self.face.v[i], self.face.v[j] ])
143         def iterateSegments(self, something):
144                 results = []
145                 for i in xrange(self.face.nPoints()):
146                         results.extend(something.haveSegment(self.segmentAt(i))) 
147                 return results
148         def compareSegments(self, something, segment):
149                 results = []
150                 for i in xrange(self.face.nPoints()):
151                         results.append(something.compareSegments([self.segmentAt(i), segment]))
152                 return results
153
154 class FaceOverlapTest:
155         def __init__(self, face1, face2):
156                 self.faces = [face1, face2]
157                 self.segments = [ LineSegments(self.faces[0]), LineSegments(self.faces[1]) ]
158         def suspectsOverlap(self):
159                 tests = self.segments[0].iterateSegments(self)
160                 gi = 0
161                 for i in tests:
162                         if( i!=None and i.intersected() ):
163                                 gi+=1
164                 return gi>0
165         def haveSegment(self, segment):
166                 return self.segments[1].compareSegments(self, segment)
167         def compareSegments(self, segments):
168                 return segments[0].intersects(segments[1])
169
170                 
171         
172 # A fold between two faces with a common edge
173 class Fold:
174         ids = -1
175         def __init__(self, parent, refPoly, poly, edge, angle=None):
176                 Fold.ids+=1
177                 self.id = Fold.ids
178                 self.refPoly = refPoly
179                 self.poly = poly
180                 self.srcFace = None
181                 self.desFace = None
182                 self.edge = edge
183                 self.foldedEdge = edge
184                 self.rm = None
185                 self.parent = parent
186                 self.tree = None
187                 if(refPoly!=None):
188                         self.refPolyNormal = refPoly.normal()
189                 self.polyNormal = poly.normal()
190                 if(angle==None):
191                         self.angle = self.calculateAngle()
192                         self.foldingPoly = poly.rotated(edge, self.angle)
193                 else:
194                         self.angle = angle
195                         self.foldingPoly = poly
196                 self.unfoldedEdge = self.edge
197                 self.unfoldedNormal = None
198                 self.animAngle = self.angle
199                 self.cr = None
200                 self.nancestors = None
201         def reset(self):
202                 self.foldingPoly = self.poly.rotated(self.edge, self.dihedralAngle())
203         def getID(self):
204                 return self.id
205         def getParent(self):
206                 return self.parent
207         def ancestors(self):
208                 if(self.nancestors==None):
209                         self.nancestors = self.computeAncestors()
210                 return self.nancestors
211         def computeAncestors(self):     
212                 if(self.parent==None):
213                         return 0
214                 else:
215                         return self.parent.ancestors()+1
216         def dihedralAngle(self):        
217                 return self.angle
218         def unfoldTo(self, f):
219                 self.animAngle = self.angle*f
220                 self.foldingPoly = self.poly.rotated(self.edge, self.animAngle)
221         def calculateAngle(self):
222                 sangle = Mathutils.AngleBetweenVecs(self.refPolyNormal, self.polyNormal)
223                 if(sangle!=sangle):
224                         sangle=0.0
225                 ncp = Mathutils.CrossVecs(self.refPolyNormal, self.polyNormal)
226                 dp = Mathutils.DotVecs(ncp, self.edge.vector)
227                 if(dp>0.0):
228                         return +sangle
229                 else:
230                         return -sangle
231         def alignWithParent(self):
232                 pass
233         def unfoldedNormal(self):
234                 return self.unfoldedNormal
235         def getEdge(self):
236                 return self.edge
237         def getFace(self):
238                 return self.poly
239         def testFace(self):
240                 return Poly.fromVectors([self.edge.v1, self.edge.v2, Vector([0,0,0])])
241         def unfoldedFace(self):
242                 return self.foldingPoly
243         def unfold(self):
244                 if(self.parent!=None):
245                         self.parent.foldFace(self)
246         def foldFace(self, child):
247                 child.foldingPoly.rotate(self.edge, self.animAngle)             
248                 if(self.parent!=None):
249                         self.parent.foldFace(child)
250                         
251 class Cut(Fold):
252         pass
253                         
254 # Trees build folds by traversing the mesh according to a local measure
255 class Tree:
256         def __init__(self, net, parent,fold,otherConstructor=None):
257                 self.net = net
258                 self.fold = fold
259                 self.face = fold.srcFace
260                 self.poly = Poly.fromBlenderFace(self.face)
261                 self.generations = net.generations
262                 self.growing = True
263                 self.tooLong = False
264                 self.parent = parent
265                 self.grown = False
266                 if not(otherConstructor):
267                         self.edges = net.edgeIteratorClass(self)
268         def goodness(self):
269                 return self.edges.goodness()
270         def compare(self, other):
271                 if(self.goodness() > other.goodness()):
272                         return +1
273                 else:
274                         return -1
275         def isGrowing(self):
276                 return self.growing
277         def beGrowing(self):
278                 self.growing = True
279         def grow(self):
280                 self.tooLong = self.fold.ancestors()>self.generations
281                 if(self.edges.hasNext() and self.growing):
282                         edge = self.edges.next()
283                         tface = self.net.facesAndEdges.takeAdjacentFace(self.face, edge)
284                         if(tface!=None):
285                                 self.branch(tface, edge)
286                         if(self.parent==None):
287                                 self.grow()
288                 else:
289                         self.grown = True
290         def isGrown(self):
291                 return self.grown
292         def canGrow(self):
293                 return (self.parent!=None and self.parent.grown)
294         def getNet(self):
295                 return self.net
296         def getFold(self):
297                 return self.fold
298         def getFace(self):
299                 return self.face
300         def branch(self, tface, edge):
301                 fold = Fold(self.fold, self.poly, Poly.fromBlenderFace(tface), edge)
302                 fold.srcFace = tface
303                 self.net.myFacesVisited+=1
304                 tree = Tree(self.net, self, fold)
305                 fold.tree = tree
306                 fold.unfold()
307                 overlaps = self.net.checkOverlaps(fold)
308                 nc = len(overlaps)
309                 self.net.overlaps+=nc
310                 if(nc>0 and self.net.avoidsOverlaps):
311                         self.handleOverlap(fold, overlaps)
312                 else:
313                         self.addFace(fold)
314         def handleOverlap(self, fold, overlaps):
315                 self.net.facesAndEdges.returnFace(fold.srcFace)
316                 self.net.myFacesVisited-=1
317                 for cfold in overlaps:
318                         ttree = cfold.tree
319                         ttree.growing = True
320                         ttree.grow()
321         def addFace(self, fold):
322                 ff = fold.unfoldedFace()
323                 fold.desFace = self.net.addFace(ff, fold.srcFace)
324                 self.net.folds.append(fold)
325                 self.net.addBranch(fold.tree)
326                 fold.tree.growing = not(self.tooLong)
327                 if(self.net.diffuse==False):
328                         fold.tree.grow()
329
330 # A Net is the result of the traversal of the mesh by Trees
331 class Net:
332         def __init__(self, src, des):
333                 self.src = src
334                 self.des = des
335                 self.firstFace = None
336                 self.firstPoly = None
337                 self.refFold = None
338                 self.edgeIteratorClass = RandomEdgeIterator
339                 if(src!=None):
340                         self.srcFaces = src.faces
341                         self.facesAndEdges = FacesAndEdges(self.src)
342                 self.myFacesVisited = 0
343                 self.facesAdded = 0
344                 self.folds = []
345                 self.cuts = []
346                 self.branches = []
347                 self.overlaps = 0
348                 self.avoidsOverlaps = True
349                 self.frame = 1
350                 self.ff = 180.0
351                 self.firstFaceIndex = None
352                 self.trees = 0
353                 self.foldIPO = None
354                 self.perFoldIPO = None
355                 self.IPOCurves = {}
356                 self.generations = 128
357                 self.diffuse = True
358                 self.noise = 0.0
359                 self.grownBranches = 0
360                 self.assignsUV = True
361                 self.animates = False
362                 self.showProgress = False
363                 self.feedback = None
364         def setSelectedFaces(self, faces):
365                 self.srcFaces = faces
366                 self.facesAndEdges = FacesAndEdges(self.srcFaces)
367         def setShowProgress(self, show):
368                 self.showProgress = show
369         # this method really needs work
370         def unfold(self):
371                 selectedFaces = [face for face in self.src.faces if (self.src.faceUV and face.flag & Mesh.FaceFlags.SELECT)]
372                 if(self.avoidsOverlaps):
373                         print "unfolding with overlap detection"
374                 if(self.firstFaceIndex==None):
375                         self.firstFaceIndex = random.randint(0, len(self.src.faces)-1)
376                 else:
377                         print "Using user-selected seed face ", self.firstFaceIndex
378                 self.firstFace = self.src.faces[self.firstFaceIndex]
379                 z = min([v.co.z for v in self.src.verts])-0.1
380                 ff = Poly.fromBlenderFace(self.firstFace)
381                 if(len(ff.v)<3):
382                         raise Exception("This mesh contains an isolated edge - it must consist only of faces")
383                 testFace = Poly.fromVectors( [ Vector([0.0,0.0,0.0]), Vector([0.0,1.0,0.0]), Vector([1.0,1.0,0.0])  ] )
384                 # hmmm
385                 u=0
386                 v=1
387                 w=2
388                 if ff.v[u].x==ff.v[u+1].x and ff.v[u].y==ff.v[u+1].y:
389                         u=1
390                         v=2
391                         w=0
392                 # here we make a couple of folds, not part of the net, which serve to get the net into the xy plane
393                 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] ] )
394                 refFace = Poly.fromVectors([ ff.v[u], ff.v[v], xyFace.v[1], xyFace.v[0] ] )
395                 xyFold =  Fold(None,   xyFace, refFace, Edge(xyFace.v[0], xyFace.v[1] ))
396                 self.refFold = Fold(xyFold, refFace, ff,         Edge(refFace.v[0], refFace.v[1] ))
397                 self.refFold.srcFace = self.firstFace
398                 trunk = Tree(self, None, self.refFold)
399                 trunk.generations = self.generations
400                 self.firstPoly = ff
401                 self.facesAndEdges.takeFace(self.firstFace)
402                 self.myFacesVisited+=1
403                 self.refFold.unfold()
404                 self.refFold.tree = trunk
405                 self.refFold.desFace = self.addFace(self.refFold.unfoldedFace(), self.refFold.srcFace)
406                 self.folds.append(self.refFold)
407                 trunk.grow()
408                 i = 0
409                 while(self.myFacesVisited<len(self.src.faces) and len(self.branches) > 0):
410                         if self.edgeIteratorClass==RandomEdgeIterator:
411                                 i = random.randint(0,len(self.branches)-1)
412                         tree = self.branches[i]
413                         if(tree.isGrown()):
414                                 self.branches.pop(i)
415                         else:
416                                 tree.beGrowing()
417                                 if(tree.canGrow()):
418                                         tree.grow()
419                                         i = 0
420                                 else:
421                                         i = (i + 1) % len(self.branches)
422                 if self.src.faceUV:
423                         for face in self.src.faces:
424                                 face.sel = False
425                         for face in selectedFaces:
426                                 face.sel = True
427                 self.src.update()
428                 Window.RedrawAll()
429         def assignUVs(self):
430                 for fold in self.folds:
431                         self.assignUV(fold.srcFace, fold.unfoldedFace())
432                 print " assigned uv to ", len(self.folds), len(self.src.faces)
433                 self.src.update()
434         def checkOverlaps(self, fold):
435                 #return self.getOverlapsBetween(fold, self.folds)
436                 return self.getOverlapsBetweenGL(fold, self.folds)
437         def getOverlapsBetween(self, fold, folds):
438                 if(fold.parent==None):
439                         return []
440                 mf = fold.unfoldedFace()
441                 c = []
442                 for afold in folds:
443                         mdf = afold.unfoldedFace()
444                         if(afold!=fold):
445                                 it1 = FaceOverlapTest(mf, mdf)
446                                 it2 = FaceOverlapTest(mdf, mf)
447                                 overlap = (it1.suspectsOverlap() or it2.suspectsOverlap())
448                                 inside = ( mdf.containsAnyOf(mf) or mf.containsAnyOf(mdf) )
449                                 if(  overlap or inside or mdf.overlays(mf)):
450                                         c.append(afold)
451                 return c
452         def getOverlapsBetweenGL(self, fold, folds):
453                 b = fold.unfoldedFace().bounds()
454                 polys = len(folds)*4+16 # the buffer is nhits, mindepth, maxdepth, name
455                 buffer = BGL.Buffer(BGL.GL_INT, polys)
456                 BGL.glSelectBuffer(polys, buffer)
457                 BGL.glRenderMode(BGL.GL_SELECT)
458                 BGL.glInitNames()
459                 BGL.glPushName(0)
460                 BGL.glPushMatrix()
461                 BGL.glMatrixMode(BGL.GL_PROJECTION)
462                 BGL.glLoadIdentity()
463                 BGL.glOrtho(b[0].x, b[1].x, b[1].y, b[0].y, 0.0, 10.0)
464                 #clip = BGL.Buffer(BGL.GL_FLOAT, 4)
465                 #clip.list = [0,0,0,0]
466                 #BGL.glClipPlane(BGL.GL_CLIP_PLANE1, clip)
467                 # could use clipping planes here too
468                 BGL.glMatrixMode(BGL.GL_MODELVIEW)
469                 BGL.glLoadIdentity()
470                 bx = (b[1].x - b[0].x)
471                 by = (b[1].y - b[0].y)
472                 cx = bx / 2.0
473                 cy = by / 2.0
474                 for f in xrange(len(folds)):
475                         afold = folds[f]
476                         if(fold!=afold):
477                                 BGL.glLoadName(f)
478                                 BGL.glBegin(BGL.GL_LINE_LOOP)
479                                 for v in afold.unfoldedFace().v:
480                                         BGL.glVertex2f(v.x, v.y)
481                                 BGL.glEnd()
482                 BGL.glPopMatrix()
483                 BGL.glFlush()
484                 hits = BGL.glRenderMode(BGL.GL_RENDER)
485                 buffer = [buffer[i] for i in xrange(3, 4*hits, 4)]
486                 o = [folds[buffer[i]] for i in xrange(len(buffer))]
487                 return self.getOverlapsBetween(fold, o)
488         def colourFace(self, face, cr):
489                 for c in face.col:
490                         c.r = int(cr[0])
491                         c.g = int(cr[1])
492                         c.b = int(cr[2])
493                         c.a = int(cr[3])
494                 self.src.update()
495         def setAvoidsOverlaps(self, avoids):
496                 self.avoidsOverlaps = avoids
497         def addBranch(self, branch):
498                 self.branches.append(branch)
499                 if self.edgeIteratorClass!=RandomEdgeIterator:
500                         self.branches.sort(lambda b1, b2: b1.compare(b2))
501         def srcSize(self):
502                 return len(self.src.faces)
503         def nBranches(self):
504                 return len(self.branches)
505         def facesCreated(self):
506                 return len(self.des.faces)
507         def facesVisited(self):
508                 return self.myFacesVisited
509         def getOverlaps(self):
510                 return self.overlaps
511         def sortOutIPOSource(self):
512                 print "Sorting out IPO"
513                 if self.foldIPO!=None:
514                         return
515                 o = None
516                 try:
517                         o = Blender.Object.Get("FoldRate")
518                 except:
519                         o = Blender.Object.New("Empty", "FoldRate")
520                         Blender.Scene.GetCurrent().objects.link(o)
521                 if(o.getIpo()==None):
522                         ipo = Blender.Ipo.New("Object", "FoldRateIPO")
523                         z = ipo.addCurve("RotZ")
524                         print " added RotZ IPO curve"
525                         z.addBezier((1,0))
526                         # again, why is this 10x out ?
527                         z.addBezier((180, self.ff/10.0))
528                         z.addBezier((361, 0.0))
529                         o.setIpo(ipo)
530                         z.recalc()
531                         z.setInterpolation("Bezier")
532                         z.setExtrapolation("Cyclic")
533                 self.setIPOSource(o)
534                 print " added IPO source"
535         def setIPOSource(self, object):
536                 try:
537                         self.foldIPO = object
538                         for i in xrange(self.foldIPO.getIpo().getNcurves()):
539                                 self.IPOCurves[self.foldIPO.getIpo().getCurves()[i].getName()] = i
540                                 print " added ", self.foldIPO.getIpo().getCurves()[i].getName()
541                 except:
542                         print "Problem setting IPO object"
543                         print sys.exc_info()[1]
544                         traceback.print_exc(file=sys.stdout)
545         def setFoldFactor(self, ff):
546                 self.ff = ff
547         def sayTree(self):
548                 for fold in self.folds:
549                         if(fold.getParent()!=None):
550                                 print fold.getID(), fold.dihedralAngle(), fold.getParent().getID()
551         def report(self):
552                 p = int(float(self.myFacesVisited)/float(len(self.src.faces)) * 100)
553                 print str(p) + "% unfolded"
554                 print "faces created:", self.facesCreated()
555                 print "faces visited:", self.facesVisited()
556                 print "originalfaces:", len(self.src.faces)
557                 n=0
558                 if(self.avoidsOverlaps):
559                         print "net avoided at least ", self.getOverlaps(), " overlaps ",
560                         n = len(self.src.faces) - self.facesCreated()
561                         if(n>0):
562                                 print "but was unable to avoid ", n, " overlaps. Incomplete net."
563                         else:
564                                 print "- A complete net."
565                 else:
566                         print "net has at least ", self.getOverlaps(), " collision(s)"
567                 return n
568         # fold all my folds to a fraction of their total fold angle
569         def unfoldToCurrentFrame(self):
570                 self.unfoldTo(Blender.Scene.GetCurrent().getRenderingContext().currentFrame())
571         def unfoldTo(self, frame):
572                 frames = Blender.Scene.GetCurrent().getRenderingContext().endFrame()
573                 if(self.foldIPO!=None and self.foldIPO.getIpo()!=None):
574                         f = self.foldIPO.getIpo().EvaluateCurveOn(self.IPOCurves["RotZ"],frame)
575                         # err, this number seems to be 10x less than it ought to be
576                         fff = 1.0 - (f*10.0 / self.ff)
577                 else:
578                         fff = 1.0-((frame)/(frames*1.0))
579                 for fold in self.folds:
580                         fold.unfoldTo(fff)
581                 for fold in self.folds:
582                         fold.unfold()
583                         tface = fold.unfoldedFace()
584                         bface = fold.desFace
585                         i = 0
586                         for v in bface.verts:
587                                 v.co.x = tface.v[i].x
588                                 v.co.y = tface.v[i].y
589                                 v.co.z = tface.v[i].z
590                                 i+=1
591                 Window.Redraw(Window.Types.VIEW3D)
592                 return None
593         def addFace(self, poly, originalFace=None):
594                 originalLength = len(self.des.verts)
595                 self.des.verts.extend([Vector(vv.x, vv.y, vv.z) for vv in poly.v])
596                 self.des.faces.extend([ range(originalLength, originalLength + poly.size()) ])
597                 newFace = self.des.faces[len(self.des.faces)-1]
598                 newFace.uv = [vv for vv in poly.v]
599                 if(originalFace!=None and self.src.vertexColors):
600                         newFace.col = [c for c in originalFace.col]
601                 if(self.feedback!=None):
602                         pu = str(int(self.fractionUnfolded() * 100))+"% unfolded"
603                         howMuchDone = str(self.myFacesVisited)+" of "+str(len(self.src.faces))+"  "+pu
604                         self.feedback.say(howMuchDone)
605                         #Window.DrawProgressBar (p, pu)
606                 if(self.showProgress):
607                         Window.Redraw(Window.Types.VIEW3D)
608                 return newFace
609         def fractionUnfolded(self):
610                 return float(self.myFacesVisited)/float(len(self.src.faces))
611         def assignUV(self, face, uv):
612                 face.uv = [Vector(v.x, v.y) for v in uv.v]
613         def unfoldAll(feedback=None):
614                 objects = Blender.Object.Get()
615                 for object in objects:
616                         if(object.getType()=='Mesh' and not(object.getName().endswith("_net")) and len(object.getData(False, True).faces)>1):
617                                 net = Net.createNet(object, feedback)
618                                 net.searchForUnfolding()
619                                 svg = SVGExporter(net, object.getName()+".svg")
620                                 svg.export()
621         unfoldAll = staticmethod(unfoldAll)
622         def searchForUnfolding(self, limit=-1):
623                 overlaps = 1
624                 attempts = 0
625                 while(overlaps > 0 or attempts<limit):
626                         self.unfold()
627                         overlaps = self.report()
628                         attempts+=1
629                 return attempts
630         def unfoldSelected(feedback=None, netName=None):
631                 return Net.createNet(Blender.Object.GetSelected()[0], feedback, netName)
632         unfoldSelected = staticmethod(unfoldSelected)
633         def clone(self, object=None):
634                 if(object==None):
635                         object = self.object
636                 net = Net.createNet(object, self.feedback)
637                 net.avoidsOverlaps = net.avoidsOverlaps
638                 return net
639         def createNet(ob, feedback=None, netName=None):
640                 mesh = ob.getData(mesh=1)
641                 netObject = None
642                 if(netName==None):
643                         netName = ob.name[0:16]+"_net"
644                 try:
645                         netObject = Blender.Object.Get(netName)
646                         netMesh = netObject.getData(False, True)
647                         if(netMesh!=None):
648                                 netMesh.verts = None # clear the mesh
649                         else:
650                                 netObject = Blender.Object.New("Mesh", netName)
651                 except:
652                         if(netObject==None):
653                                 netObject = Blender.Object.New("Mesh", netName)
654                 netMesh = netObject.getData(False, True)  # True means "as a Mesh not an NMesh"
655                 try:
656                         Blender.Scene.GetCurrent().objects.link(netObject)
657                 except:
658                         pass
659                 try:
660                         netMesh.materials = mesh.materials
661                         netMesh.vertexColors = True
662                 except:
663                         print "Problem setting materials here"
664                 net = Net(mesh, netMesh)
665                 if(mesh.faceUV and mesh.activeFace>=0 and (mesh.faces[mesh.activeFace].flag & Mesh.FaceFlags.SELECT)):
666                         net.firstFaceIndex = mesh.activeFace
667                 net.object = ob
668                 net.feedback = feedback
669                 return net
670         createNet = staticmethod(createNet)
671         def importNet(filename):
672                 netName = filename.rstrip(".svg").replace("\\","/")
673                 netName = netName[netName.rfind("/")+1:]
674                 try:
675                         netObject = Blender.Object.Get(netName)
676                 except:
677                         netObject  = Blender.Object.New("Mesh", netName)
678                 netObject.getData(mesh=1).name = netName
679                 try:
680                         Blender.Scene.GetCurrent().objects.link(netObject)
681                 except:
682                         pass
683                 net = Net(None, netObject.getData(mesh=1))
684                 handler = NetHandler(net)
685                 xml.sax.parse(filename, handler)
686                 Window.Redraw(Window.Types.VIEW3D)
687                 return net
688         importNet = staticmethod(importNet)
689         def getSourceMesh(self):
690                 return self.src
691                 
692 # determines the order in which to visit faces according to a local measure             
693 class EdgeIterator:
694         def __init__(self, branch, otherConstructor=None):
695                 self.branch = branch
696                 self.bface = branch.getFace()
697                 self.edge = branch.getFold().getEdge()
698                 self.net = branch.getNet()
699                 self.n = len(self.bface)
700                 self.edges = []
701                 self.i = 0
702                 self.gooodness = 0
703                 self.createEdges()
704                 self.computeGoodness()
705                 if(otherConstructor==None):
706                         self.sequenceEdges()
707         def createEdges(self):
708                 edge = None
709                 e = Edge.edgesOfBlenderFace(self.net.getSourceMesh(), self.bface)
710                 for edge in e:
711                         if not(edge.isBlenderSeam() and edge!=self.edge):
712                                 self.edges.append(edge)
713         def sequenceEdges(self):
714                 pass
715         def next(self):
716                 edge = self.edges[self.i]
717                 self.i+=1
718                 return edge
719         def size(self):
720                 return len(self.edges)
721         def reset(self):
722                 self.i = 0
723         def hasNext(self):
724                 return (self.i<len(self.edges))
725         def goodness(self):
726                 return self.gooodness
727         def computeGoodness(self):
728                 self.gooodness = 0
729         def rotate(self):
730                 self.edges.append(self.edges.pop(0))
731
732 class RandomEdgeIterator(EdgeIterator):
733         def sequenceEdges(self):
734                 random.seed()
735                 random.shuffle(self.edges)
736         def goodness(self):
737                 return random.randint(0, self.net.srcSize())
738         
739                 
740 class Largest(EdgeIterator):
741         def sequenceEdges(self):
742                 for e in self.edges:
743                         f = self.net.facesAndEdges.findAdjacentFace(self.bface, e)
744                         if(f!=None):
745                                 e.setGoodness(f.area)
746                 self.edges.sort(lambda e1, e2: -e1.compare(e2))
747         def computeGoodness(self):
748                 self.gooodness = self.bface.area
749                 
750                         
751 class Brightest(EdgeIterator):
752         def sequenceEdges(self):
753                 for edge in self.edges:
754                         f = self.net.facesAndEdges.findAdjacentFace(self.bface, edge)
755                         if(f!=None):
756                                 b = 0
757                                 if self.net.src.vertexColors:
758                                         for c in f.col:
759                                                 b+=(c.g+c.r+c.b)
760                                 rc = float(random.randint(0, self.net.srcSize())) / float(self.net.srcSize()) / 100.0
761                                 b+=rc
762                                 edge.setGoodness(b)
763                 self.edges.sort(lambda e1, e2: e1.compare(e2))
764         def computeGoodness(self):
765                 g = 0
766                 if self.net.src.vertexColors:
767                         for c in self.bface.col:
768                                 g+=(c.g+c.r+c.b)
769                 self.gooodness = g
770                 
771 class OddEven(EdgeIterator):
772         i = True
773         def sequenceEdges(self):
774                 OddEven.i = not(OddEven.i)
775                 if(OddEven.i):
776                         self.edges.reverse()
777                 
778 class Curvature(EdgeIterator):
779         def sequenceEdges(self):
780                 p1 = Poly.fromBlenderFace(self.bface)
781                 gg = 0.0
782                 for edge in self.edges:
783                         f = self.net.facesAndEdges.findAdjacentFace(self.bface, edge)
784                         if(f!=None):
785                                 p2 = Poly.fromBlenderFace(f)
786                                 fold = Fold(None, p1, p2, edge)
787                                 fold.srcFace = f
788                                 b = Tree(self.net, self.branch, fold, self)
789                                 c = Curvature(b, False)
790                                 g = c.goodness()
791                                 gg+=g
792                                 edge.setGoodness(g)
793                 self.edges.sort(lambda e1, e2: e1.compare(e2))
794                 tg = (self.gooodness + gg)
795                 rc = float(random.randint(0, self.net.srcSize())) / float(self.net.srcSize()) / 100.0
796                 if(tg!=0.0):
797                         self.gooodness = self.gooodness + rc / tg
798         def computeGoodness(self):
799                 g = 0
800                 for edge in self.edges:
801                         f = self.net.facesAndEdges.findAdjacentFace(self.bface, edge)
802                         if(f!=None):
803                                 p1 = Poly.fromBlenderFace(self.bface)
804                                 p2 = Poly.fromBlenderFace(f)
805                                 f = Fold(None, p1, p2, edge)
806                                 g += f.dihedralAngle()
807                 self.gooodness = g
808                 
809         
810 class Edge:
811         def __init__(self, v1=None, v2=None, mEdge=None, i=-1):
812                 self.idx = i
813                 if v1 and v2:
814                         self.v1 = v1.copy()
815                         self.v2 = v2.copy()
816                 else:
817                         self.v1 = mEdge.v1.co.copy()
818                         self.v2 = mEdge.v2.co.copy()
819                 self.v1n = -self.v1
820                 self.vector = self.v1-self.v2
821                 self.vector.resize3D()
822                 self.vector.normalize()
823                 self.bmEdge = mEdge
824                 self.gooodness = 0.0
825         def fromBlenderFace(mesh, bface, i):
826                 if(i>len(bface)-1):
827                         return None
828                 if(i==len(bface)-1):
829                         j = 0
830                 else:
831                         j = i+1
832                 edge =  Edge( bface.v[i].co.copy(), bface.v[j].co.copy() )
833                 edge.bEdge = mesh.findEdge(bface.v[i], bface.v[j])
834                 edge.idx = i
835                 return edge
836         fromBlenderFace=staticmethod(fromBlenderFace)
837         def edgesOfBlenderFace(mesh, bmFace):
838                 edges = [mesh.edges[mesh.findEdges(edge[0], edge[1])] for edge in bmFace.edge_keys]
839                 v = bmFace.verts
840                 e = []
841                 vi = v[0]
842                 i=0
843                 for j in xrange(1, len(bmFace)+1):
844                         vj = v[j%len(bmFace)]
845                         for ee in edges:
846                                 if((ee.v1.index==vi.index and ee.v2.index==vj.index) or (ee.v2.index==vi.index and ee.v1.index==vj.index)):
847                                         e.append(Edge(vi.co, vj.co, ee, i))
848                                         i+=1
849                         vi = vj
850                 return e
851         edgesOfBlenderFace=staticmethod(edgesOfBlenderFace)
852         def isBlenderSeam(self):
853                 return (self.bmEdge.flag & Mesh.EdgeFlags.SEAM)
854         def isInFGon(self):
855                 return (self.bmEdge.flag & Mesh.EdgeFlags.FGON)
856         def mapTo(self, poly):
857                 if(self.idx==len(poly.v)-1):
858                         j = 0
859                 else:
860                         j = self.idx+1
861                 return Edge(poly.v[self.idx], poly.v[j])
862         def isDegenerate(self):
863                 return self.vector.length==0
864         def vertices(s):
865                 return [ [s.v1.x, s.v1.y, s.v1.z], [s.v2.x, s.v2.y,s.v2.z] ]
866         def key(self):
867                 return self.bmEdge.key
868         def goodness(self):
869                 return self.gooodness
870         def setGoodness(self, g):
871                 self.gooodness = g
872         def compare(self, other):
873                 if(self.goodness() > other.goodness()):
874                         return +1
875                 else:
876                         return -1
877         
878 class Poly:
879         ids = -1
880         def __init__(self):
881                 Poly.ids+=1
882                 self.v = []
883                 self.id = Poly.ids
884                 self.boundz = None
885         def getID(self):
886                 return self.id
887         def normal(self):
888                 a =self.v[0]
889                 b=self.v[1]
890                 c=self.v[2]
891                 p = b-a
892                 p.resize3D()
893                 q = a-c
894                 q.resize3D()
895                 return CrossVecs(p,q)
896         def isBad(self):
897                 badness = 0
898                 for vv in self.v:
899                         if(vv.x!=vv.x or vv.y!=vv.y or vv.z!=vv.z): # Nan check
900                                 badness+=1
901                 return (badness>0)
902         def midpoint(self):
903                 x=y=z = 0.0
904                 n = 0
905                 for vv in self.v:
906                         x+=vv.x
907                         y+=vv.y
908                         z+=vv.z
909                         n+=1
910                 return [ x/n, y/n, z/n ]
911         def centerAtOrigin(self):
912                 mp = self.midpoint()
913                 mp = -mp
914                 toOrigin = TranslationMatrix(mp)
915                 self.v = [(vv * toOrigin) for vv in self.v]
916         def move(self, tv):
917                 mv = TranslationMatrix(tv)
918                 self.v = [(vv * mv) for vv in self.v]
919         def scale(self, s):
920                 mp = Vector(self.midpoint())
921                 fromOrigin = TranslationMatrix(mp)
922                 mp = -mp
923                 toOrigin = TranslationMatrix(mp)
924                 sm = ScaleMatrix(s, 4)
925                 # Todo, the 3 lines below in 1 LC
926                 self.v = [(vv * toOrigin) for vv in self.v]
927                 self.v = [(sm * vv) for vv in self.v]
928                 self.v = [(vv * fromOrigin) for vv in self.v]
929         def nPoints(self):
930                 return len(self.v)
931         def size(self):
932                 return len(self.v)
933         def rotated(self, axis, angle):
934                 p = self.clone()
935                 p.rotate(axis, angle)
936                 return p
937         def rotate(self, axis, angle):
938                 rotation = RotationMatrix(angle, 4, "r", axis.vector)
939                 toOrigin = TranslationMatrix(axis.v1n)
940                 fromOrigin = TranslationMatrix(axis.v1)
941                 # Todo, the 3 lines below in 1 LC
942                 self.v = [(vv * toOrigin) for vv in self.v]
943                 self.v = [(rotation * vv) for vv in self.v]
944                 self.v = [(vv * fromOrigin) for vv in self.v]
945         def moveAlong(self, vector, distance):
946                 t = TranslationMatrix(vector)
947                 s = ScaleMatrix(distance, 4)
948                 ts = t*s
949                 self.v = [(vv * ts) for vv in self.v]
950         def bounds(self):
951                 if(self.boundz == None):
952                         vv = [vv for vv in self.v]
953                         vv.sort(key=lambda v: v.x)
954                         minx = vv[0].x
955                         maxx = vv[len(vv)-1].x
956                         vv.sort(key=lambda v: v.y)
957                         miny = vv[0].y
958                         maxy = vv[len(vv)-1].y
959                         self.boundz = [Vector(minx, miny, 0), Vector(maxx, maxy, 0)]
960                 return self.boundz
961         def fromBlenderFace(bface):
962                 p = Poly()
963                 for vv in bface.v:
964                         vec = Vector([vv.co[0], vv.co[1], vv.co[2] , 1.0]) 
965                         p.v.append(vec)
966                 return p
967         fromBlenderFace = staticmethod(fromBlenderFace)
968         def fromList(list):
969                 p = Poly()
970                 for vv in list:
971                         vec = Vector( [vvv for vvv in vv] )
972                         vec.resize4D()
973                         p.v.append(vec)
974                 return p
975         fromList = staticmethod(fromList)
976         def fromVectors(vectors):
977                 p = Poly()
978                 p.v.extend([v.copy().resize4D() for v in vectors])
979                 return p
980         fromVectors = staticmethod(fromVectors)
981         def clone(self):
982                 p = Poly()
983                 p.v.extend(self.v)
984                 return p
985         def hasVertex(self, ttv):
986                 v = Mathutils.Vector(ttv)
987                 v.normalize()
988                 for tv in self.v:
989                         vv = Mathutils.Vector(tv)
990                         vv.normalize()
991                         t = 0.00001
992                         if abs(vv.x-v.x)<t and abs(vv.y-v.y)<t:
993                                 return True
994                 return False
995         def overlays(self, poly):
996                 if len(poly.v)!=len(self.v):
997                         return False
998                 c = 0
999                 for point in poly.v:
1000                         if self.hasVertex(point):
1001                                 c+=1
1002                 return c==len(self.v)
1003         def sharesVertexWith(self, poly):
1004                 for point in poly.v:
1005                         if(self.hasVertex(point)):
1006                                 return True
1007                 return False
1008         def containsAnyOf(self, poly):
1009                 for point in poly.v:
1010                         if(not(self.hasVertex(point))):
1011                                 if self.contains(point):
1012                                         return True
1013                 return False
1014         def toString(self):
1015                 return self.v
1016         # This is the BEST algorithm for point-in-polygon detection.
1017         # It's by W. Randolph Franklin. It's also very beautiful (looks even better in C).
1018         # All the others are shite; they give false positives.
1019         # returns 1 for inside, 1 or 0 for edges
1020         def contains(self, tp):
1021                 c = 0
1022                 j = len(self.v)-1
1023                 for i in xrange(len(self.v)):
1024                         if(i>0): j=i-1
1025                         cv = self.v[i]
1026                         nv = self.v[j]
1027                         if ((((cv.y<=tp.y) and (tp.y<nv.y)) or ((nv.y<=tp.y) and (tp.y<cv.y))) and (tp.x < (nv.x - cv.x) * (tp.y - cv.y) / (nv.y - cv.y) + cv.x)):
1028                                 c = not(c)
1029                 return (c == 1)
1030                 
1031 class SVGExporter:
1032         def __init__(self, net, filename):
1033                 self.net = net
1034                 print self.net.des.name
1035                 self.object = self.net.object
1036                 print "Exporting ", self.object
1037                 self.filename = filename
1038                 self.file = None
1039                 self.e = None
1040                 self.width = 1024
1041                 self.height = 768
1042         def start(self):
1043                 print "Exporting SVG to ", self.filename
1044                 self.file = open(self.filename, 'w')
1045                 self.e = xml.sax.saxutils.XMLGenerator(self.file, "UTF-8")
1046                 atts = {}
1047                 atts["width"] = "100%"
1048                 atts["height"] = "100%"
1049                 atts["viewBox"] = str(self.vxmin)+" "+str(self.vymin)+" "+str(self.vxmax-self.vxmin)+" "+str(self.vymax-self.vymin)
1050                 atts["xmlns:nets"] = "http://celeriac.net/unfolder/rdf#"
1051                 atts["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
1052                 atts["xmlns"] ="http://www.w3.org/2000/svg"
1053                 a = xml.sax.xmlreader.AttributesImpl(atts)
1054                 self.e.startDocument()
1055                 self.e.startElement("svg", a)
1056                 self.e.startElement("defs", xml.sax.xmlreader.AttributesImpl({}))
1057                 atts = {}
1058                 atts["type"]="text/css"
1059                 self.e.startElement("style", atts)
1060                 # can't find a proper way to do this
1061                 self.file.write("<![CDATA[")
1062                 self.file.write("polygon.poly{fill:white;stroke:black;stroke-width: 0.001}")
1063                 self.file.write("g#foldLines line.valley{stroke:white;stroke-width:0.01;stroke-dasharray:0.02,0.01,0.02,0.05}")
1064                 self.file.write("g#foldLines line.mountain{stroke:white;stroke-width:0.01;stroke-dasharray:0.02,0.04}")
1065                 self.file.write("]]>")
1066                 self.e.endElement("style")
1067                 self.e.endElement("defs")
1068                 #self.addClipPath()
1069                 self.addMeta()
1070         def addMeta(self):
1071                 self.e.startElement("metadata", xml.sax.xmlreader.AttributesImpl({}))
1072                 self.e.startElement("nets:net", xml.sax.xmlreader.AttributesImpl({}))
1073                 for i in xrange(1, len(self.net.folds)):
1074                         fold = self.net.folds[i]
1075                         # AttributesNSImpl - documentation is rubbish. using this hack.
1076                         atts = {}
1077                         atts["nets:id"] = "fold"+str(fold.getID())
1078                         if(fold.parent!=None):
1079                                 atts["nets:parent"] = "fold"+str(fold.parent.getID())
1080                         else:
1081                                 atts["nets:parent"] = "null"
1082                         atts["nets:da"] = str(fold.dihedralAngle())
1083                         if(fold.parent!=None):
1084                                 atts["nets:ofPoly"] = "poly"+str(fold.parent.foldingPoly.getID())
1085                         else:
1086                                 atts["nets:ofPoly"] = ""
1087                         atts["nets:toPoly"] = "poly"+str(fold.foldingPoly.getID())
1088                         a = xml.sax.xmlreader.AttributesImpl(atts)
1089                         self.e.startElement("nets:fold",  a)
1090                         self.e.endElement("nets:fold")
1091                 self.e.endElement("nets:net")
1092                 self.e.endElement("metadata")
1093         def end(self):
1094                 self.e.endElement("svg")
1095                 self.e.endDocument()
1096                 print "grown."
1097         def export(self):
1098                 self.net.unfoldTo(1)
1099                 bb = self.object.getBoundBox()
1100                 self.vxmin = bb[0][0]
1101                 self.vymin = bb[0][1]
1102                 self.vxmax = bb[7][0]
1103                 self.vymax = bb[7][1]
1104                 self.start()
1105                 atts = {}
1106                 atts["id"] = self.object.getName()
1107                 a = xml.sax.xmlreader.AttributesImpl(atts)
1108                 self.e.startElement("g", a)
1109                 #self.addUVImage()
1110                 self.addPolys()
1111                 self.addFoldLines()
1112                 #self.addCutLines()
1113                 self.e.endElement("g")
1114                 self.end()
1115         def addClipPath(self):
1116                 atts = {}
1117                 atts["id"] = "netClip"
1118                 atts["clipPathUnits"] = "userSpaceOnUse"
1119                 atts["x"] = str(self.vxmin)
1120                 atts["y"] = str(self.vymin)
1121                 atts["width"] = "100%"
1122                 atts["height"] = "100%"
1123                 self.e.startElement("clipPath", atts)
1124                 self.addPolys()
1125                 self.e.endElement("clipPath")
1126         def addUVImage(self):
1127                 image = Blender.Image.GetCurrent()
1128                 if image==None:
1129                         return
1130                 ifn = image.getFilename()
1131                 #ifn = self.filename.replace(".svg", ".jpg")
1132                 #image.setFilename(ifn)
1133                 #ifn = ifn[ifn.rfind("/")+1:]
1134                 #image.save()
1135                 atts = {}
1136                 atts["clip-path"] = "url(#netClip)"
1137                 atts["xlink:href"] = ifn
1138                 self.e.startElement("image", atts)
1139                 self.e.endElement("image")
1140         def addPolys(self):
1141                 atts = {}
1142                 atts["id"] = "polys"
1143                 a = xml.sax.xmlreader.AttributesImpl(atts)
1144                 self.e.startElement("g", a)
1145                 for i in xrange(len(self.net.folds)):
1146                         self.addPoly(self.net.folds[i])
1147                 self.e.endElement("g")
1148         def addFoldLines(self):
1149                 atts = {}
1150                 atts["id"] = "foldLines"
1151                 a = xml.sax.xmlreader.AttributesImpl(atts)
1152                 self.e.startElement("g", a)
1153                 for i in xrange( 1, len(self.net.folds)):
1154                         self.addFoldLine(self.net.folds[i])
1155                 self.e.endElement("g")
1156         def addFoldLine(self, fold):
1157                 edge = fold.edge.mapTo(fold.parent.foldingPoly)
1158                 if fold.dihedralAngle()>0:
1159                         foldType="valley"
1160                 else:
1161                         foldType="mountain"
1162                 atts={}
1163                 atts["x1"] = str(edge.v1.x)
1164                 atts["y1"] = str(edge.v1.y)
1165                 atts["x2"] = str(edge.v2.x)
1166                 atts["y2"] = str(edge.v2.y)
1167                 atts["id"] = "fold"+str(fold.getID())
1168                 atts["class"] = foldType
1169                 a = xml.sax.xmlreader.AttributesImpl(atts)
1170                 self.e.startElement("line", a)
1171                 self.e.endElement("line")
1172         def addCutLines(self):
1173                 atts = {}
1174                 atts["id"] = "cutLines"
1175                 a = xml.sax.xmlreader.AttributesImpl(atts)
1176                 self.e.startElement("g", a)
1177                 for i in xrange( 1, len(self.net.cuts)):
1178                         self.addCutLine(self.net.cuts[i])
1179                 self.e.endElement("g")
1180         def addCutLine(self, cut):
1181                 edge = cut.edge.mapTo(cut.parent.foldingPoly)
1182                 if cut.dihedralAngle()>0:
1183                         foldType="valley"
1184                 else:
1185                         foldType="mountain"
1186                 atts={}
1187                 atts["x1"] = str(edge.v1.x)
1188                 atts["y1"] = str(edge.v1.y)
1189                 atts["x2"] = str(edge.v2.x)
1190                 atts["y2"] = str(edge.v2.y)
1191                 atts["id"] = "cut"+str(cut.getID())
1192                 atts["class"] = foldType
1193                 a = xml.sax.xmlreader.AttributesImpl(atts)
1194                 self.e.startElement("line", a)
1195                 self.e.endElement("line")
1196         def addPoly(self, fold):
1197                 face = fold.foldingPoly
1198                 atts = {}
1199                 if fold.desFace.col:
1200                         col = fold.desFace.col[0]
1201                         rgb = "rgb("+str(col.r)+","+str(col.g)+","+str(col.b)+")"
1202                         atts["fill"] = rgb
1203                 atts["class"] = "poly"
1204                 atts["id"] = "poly"+str(face.getID())
1205                 points = ""
1206                 first = True
1207                 for vv in face.v:
1208                         if(not(first)):
1209                                 points+=','
1210                         first = False
1211                         points+=str(vv[0])
1212                         points+=' '
1213                         points+=str(vv[1])
1214                 atts["points"] = points
1215                 a = xml.sax.xmlreader.AttributesImpl(atts)
1216                 self.e.startElement("polygon", a)
1217                 self.e.endElement("polygon")
1218         def fileSelected(filename):
1219                 try:
1220                         net = Registry.GetKey('unfolder')['net']
1221                         exporter = SVGExporter(net, filename)
1222                         exporter.export()
1223                 except:
1224                         print "Problem exporting SVG"
1225                         traceback.print_exc(file=sys.stdout)
1226         fileSelected = staticmethod(fileSelected)       
1227
1228
1229 class NetHandler(xml.sax.handler.ContentHandler):
1230         def __init__(self, net):
1231                 self.net = net
1232                 self.first = (41==41)
1233                 self.currentElement = None
1234                 self.chars = None
1235                 self.currentAction = None
1236                 self.foldsPending = {}
1237                 self.polys = {}
1238                 self.actions = {}
1239                 self.actions["nets:fold"] = self.foldInfo
1240                 self.actions["line"] = self.cutOrFold
1241                 self.actions["polygon"] = self.createPoly
1242         def setDocumentLocator(self, locator):
1243                 pass
1244         def startDocument(self):
1245                 pass
1246         def endDocument(self):
1247                 for fold in self.foldsPending.values():
1248                         face = self.net.addFace(fold.unfoldedFace())
1249                         fold.desFace = face
1250                         self.net.folds.append(fold)
1251                 self.net.addFace(self.first)
1252                 self.foldsPending = None
1253                 self.polys = None
1254         def startPrefixMapping(self, prefix, uri):
1255                 pass
1256         def endPrefixMapping(self, prefix):
1257                 pass
1258         def startElement(self, name, attributes):
1259                 self.currentAction = None
1260                 try:
1261                         self.currentAction = self.actions[name]
1262                 except:
1263                         pass
1264                 if(self.currentAction!=None):
1265                         self.currentAction(attributes)
1266         def endElement(self, name):
1267                 pass
1268         def startElementNS(self, name, qname, attrs):
1269                 self.currentAction = self.actions[name]
1270                 if(self.currentAction!=None):
1271                         self.currentAction(attributes)
1272         def endElementNS(self, name, qname):
1273                 pass
1274         def characters(self, content):
1275                 pass
1276         def ignorableWhitespace(self):
1277                 pass
1278         def processingInstruction(self, target, data):
1279                 pass
1280         def skippedEntity(self, name):
1281                 pass
1282         def foldInfo(self, atts):
1283                 self.foldsPending[atts["nets:id"]] = atts
1284         def createPoly(self, atts):
1285                 xy = re.split('[, ]' , atts["points"])
1286                 vectors = []
1287                 for i in xrange(0, len(xy)-1, 2):
1288                         v = Vector([float(xy[i]), float(xy[i+1]), 0.0])
1289                         vectors.append(v)
1290                 poly = Poly.fromVectors(vectors)
1291                 if(self.first==True):
1292                         self.first = poly
1293                 self.polys[atts["id"]] = poly
1294         def cutOrFold(self, atts):
1295                 fid = atts["id"]
1296                 try:
1297                         fi = self.foldsPending[fid]
1298                 except:
1299                         pass
1300                 p1 = Vector([float(atts["x1"]), float(atts["y1"]), 0.0])
1301                 p2 = Vector([float(atts["x2"]), float(atts["y2"]), 0.0])
1302                 edge = Edge(p1, p2)
1303                 parent = None
1304                 ofPoly = None
1305                 toPoly = None
1306                 try: 
1307                         parent = self.foldsPending[fi["nets:parent"]]
1308                 except:
1309                         pass
1310                 try:
1311                         ofPoly = self.polys[fi["nets:ofPoly"]]
1312                 except:
1313                         pass
1314                 try:
1315                         toPoly = self.polys[fi["nets:toPoly"]]
1316                 except:
1317                         pass
1318                 fold = Fold(parent, ofPoly , toPoly, edge, float(fi["nets:da"]))
1319                 self.foldsPending[fid] = fold
1320         def fileSelected(filename):
1321                 try:
1322                         net = Net.importNet(filename)
1323                         try:
1324                                 Registry.GetKey('unfolder')['net'] = net
1325                         except:
1326                                 Registry.SetKey('unfolder', {})
1327                                 Registry.GetKey('unfolder')['net'] = net
1328                         Registry.GetKey('unfolder')['lastpath'] = filename
1329                 except:
1330                         print "Problem importing SVG"
1331                         traceback.print_exc(file=sys.stdout)
1332         fileSelected = staticmethod(fileSelected)               
1333
1334
1335 class GUI:
1336         def __init__(self):
1337                 self.overlaps = Draw.Create(0)
1338                 self.ani = Draw.Create(0)
1339                 self.selectedFaces =0
1340                 self.search = Draw.Create(0)
1341                 self.diffuse = True
1342                 self.ancestors = Draw.Create(0)
1343                 self.noise = Draw.Create(0.0)
1344                 self.shape = Draw.Create(0)
1345                 self.nOverlaps = 1==2
1346                 self.iterators = [RandomEdgeIterator,Brightest,Curvature,EdgeIterator,OddEven,Largest]
1347                 self.iterator = RandomEdgeIterator
1348                 self.overlapsText = "*"
1349                 self.message = " "
1350         def makePopupGUI(self):
1351                 useRandom = Draw.Create(0)
1352                 pub = []
1353                 pub.append(("Search", self.search, "Search for non-overlapping net (maybe forever)"))
1354                 pub.append(("Random", useRandom, "Random style net"))
1355                 ok = True
1356                 while ok:
1357                         ok = Blender.Draw.PupBlock("Unfold", pub)
1358                         if ok:
1359                                 if useRandom.val:
1360                                         self.iterator = RandomEdgeIterator
1361                                 else:
1362                                         self.iterator = Curvature
1363                                 self.unfold()
1364         def makeStandardGUI(self):
1365                 Draw.Register(self.draw, self.keyOrMouseEvent, self.buttonEvent)
1366         def installScriptLink(self):
1367                 print "Adding script link for animation"
1368                 s = Blender.Scene.GetCurrent().getScriptLinks("FrameChanged")
1369                 if(s!=None and s.count("frameChanged.py")>0):
1370                         return
1371                 try:
1372                         script = Blender.Text.Get("frameChanged.py")
1373                 except:
1374                         script = Blender.Text.New("frameChanged.py")
1375                         script.write("import Blender\n")
1376                         script.write("import mesh_unfolder as Unfolder\n")
1377                         script.write("u = Blender.Registry.GetKey('unfolder')\n")
1378                         script.write("if u!=None:\n")
1379                         script.write("\tn = u['net']\n")
1380                         script.write("\tif(n!=None and n.animates):\n")
1381                         script.write("\t\tn.unfoldToCurrentFrame()\n")
1382                 Blender.Scene.GetCurrent().addScriptLink("frameChanged.py", "FrameChanged")
1383         def unfold(self):
1384                 anc = self.ancestors.val
1385                 n = 0.0
1386                 s = True
1387                 self.nOverlaps = 0
1388                 searchLimit = 10
1389                 search = 1
1390                 Draw.Redraw(1)
1391                 net = None
1392                 name = None
1393                 try:
1394                         self.say("Unfolding...")
1395                         Draw.Redraw(1)
1396                         while(s):# and search < searchLimit):
1397                                 if(net!=None):
1398                                         name = net.des.name
1399                                 net = Net.unfoldSelected(self, name)
1400                                 net.setAvoidsOverlaps(not(self.overlaps.val))
1401                                 print
1402                                 print "Unfolding selected object"
1403                                 net.edgeIteratorClass = self.iterator
1404                                 print "Using ", net.edgeIteratorClass
1405                                 net.animates = self.ani.val
1406                                 self.diffuse = (self.ancestors.val==0)
1407                                 net.diffuse = self.diffuse
1408                                 net.generations = self.ancestors.val
1409                                 net.noise = self.noise.val
1410                                 print "even:", net.diffuse, " depth:", net.generations
1411                                 net.unfold()
1412                                 n = net.report()
1413                                 t = "."
1414                                 if(n<1.0):
1415                                         t = "Overlaps>="+str(n)
1416                                 else:
1417                                         t = "A complete net."
1418                                 self.nOverlaps = (n>=1)
1419                                 if(self.nOverlaps):
1420                                         self.say(self.message+" - unfolding failed - try again ")
1421                                 elif(not(self.overlaps.val)):
1422                                         self.say("Success. Complete net - no overlaps ")
1423                                 else:
1424                                         self.say("Unfolding complete")
1425                                 self.ancestors.val = anc
1426                                 s = (self.search.val and n>=1.0)
1427                                 dict = Registry.GetKey('unfolder')
1428                                 if(not(dict)):
1429                                         dict = {}
1430                                 dict['net'] = net
1431                                 Registry.SetKey('unfolder', dict)
1432                                 if(s):
1433                                         net = net.clone()
1434                                 search += 1
1435                 except(IndexError):
1436                         self.say("Please select an object to unfold")
1437                 except:
1438                         self.say("Problem unfolding selected object - see console for details")
1439                         print "Problem unfolding selected object:"
1440                         print sys.exc_info()[1]
1441                         traceback.print_exc(file=sys.stdout)
1442                 if(self.ani):
1443                         if Registry.GetKey('unfolder')==None:
1444                                 print "no net!"
1445                                 return
1446                         Registry.GetKey('unfolder')['net'].sortOutIPOSource()
1447                         self.installScriptLink()
1448                 Draw.Redraw(1)
1449         def keyOrMouseEvent(self, evt, val):
1450                 if (evt == Draw.ESCKEY and not val):
1451                         Draw.Exit()
1452         def buttonEvent(self, evt):
1453                 if (evt == 1):
1454                         self.unfold()
1455                 if (evt == 5):
1456                         try:
1457                                 Registry.GetKey('unfolder')['net'].setAvoidsOverlaps(self.overlaps.val)
1458                         except:
1459                                 pass
1460                 if (evt == 2):
1461                         print "Trying to set IPO curve"
1462                         try:
1463                                 s = Blender.Object.GetSelected()
1464                                 if(s!=None):
1465                                         Registry.GetKey('unfolder')['net'].setIPOSource( s[0] )
1466                                         print "Set IPO curve"
1467                                 else:
1468                                         print "Please select an object to use the IPO of"
1469                         except:
1470                                 print "Problem setting IPO source"
1471                         Draw.Redraw(1)
1472                 if (evt == 6):
1473                         Draw.Exit()
1474                 if (evt == 7):
1475                         try:
1476                                 if (Registry.GetKey('unfolder')['net']!=None):
1477                                         Registry.GetKey('unfolder')['net'].animates = self.ani.val
1478                                         if(self.ani):
1479                                                 Registry.GetKey('unfolder')['net'].sortOutIPOSource()
1480                                                 self.installScriptLink()
1481                         except:
1482                                 print sys.exc_info()[1]
1483                                 traceback.print_exc(file=sys.stdout)
1484                         Draw.Redraw(1)
1485                 if (evt == 19):
1486                         pass
1487                 if (evt == 87):
1488                         try:
1489                                 if (Registry.GetKey('unfolder')['net']!=None):
1490                                         Registry.GetKey('unfolder')['net'].assignUVs()
1491                                         self.say("Assigned UVs")
1492                         except:
1493                                 print sys.exc_info()[1]
1494                                 traceback.print_exc(file=sys.stdout)
1495                         Draw.Redraw(1)
1496                 if(evt==91):
1497                         if( testOverlap() == True):
1498                                 self.nOverlaps = 1
1499                         else:
1500                                 self.nOverlaps = 0
1501                         Draw.Redraw(1)
1502                 if(evt==714):
1503                         Net.unfoldAll(self)
1504                         Draw.Redraw(1)
1505                 if(evt==713):
1506                         self.iterator = self.iterators[self.shape.val]
1507                         Draw.Redraw(1)
1508                 if(evt==92):
1509                         if( testContains() == True):
1510                                 self.nOverlaps = 1
1511                         else:
1512                                 self.nOverlaps = 0
1513                         Draw.Redraw(1)
1514                 if(evt==104):
1515                         try:
1516                                 filename = "net.svg"
1517                                 s = Blender.Object.GetSelected()
1518                                 if(s!=None and len(s)>0):
1519                                         filename = s[0].getName()+".svg"
1520                                 else:
1521                                         if (Registry.GetKey('unfolder')['net']!=None):
1522                                                 filename = Registry.GetKey('unfolder')['net'].des.name
1523                                                 if(filename==None):
1524                                                         filename="net.svg"
1525                                                 else:
1526                                                         filename=filename+".svg"
1527                                 Window.FileSelector(SVGExporter.fileSelected, "Select filename", filename)
1528                         except:
1529                                 print "Problem exporting SVG"
1530                                 traceback.print_exc(file=sys.stdout)
1531                 if(evt==107):
1532                         try:
1533                                 Window.FileSelector(NetHandler.fileSelected, "Select file")
1534                         except:
1535                                 print "Problem importing SVG"
1536                                 traceback.print_exc(file=sys.stdout)
1537         def say(self, m):
1538                 self.message = m
1539                 Draw.Redraw(1)
1540                 Window.Redraw(Window.Types.SCRIPT)
1541         def draw(self):
1542                 cw = 64
1543                 ch = 16
1544                 l = FlowLayout(32, cw, ch, 350, 64)
1545                 l.y = 70
1546                 self.search = Draw.Toggle("search",     19,   l.nx(), l.ny(), l.cw, l.ch, self.search.val, "Search for non-overlapping mesh (potentially indefinitely)")
1547                 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")
1548                 self.ani = Draw.Toggle("ani",       7,   l.nx(), l.ny(), l.cw, l.ch, self.ani.val, "Animate net")
1549                 Draw.Button("uv",               87,   l.nx(), l.ny(), l.cw, l.ch, "Assign net as UV to source mesh (overwriting existing UV)")
1550                 Draw.Button("Unfold",           1, l.nx(), l.ny(), l.cw, l.ch, "Unfold selected mesh to net")
1551                 Draw.Button("save",             104,   l.nx(), l.ny(), l.cw, l.ch,  "Save net as SVG")
1552                 Draw.Button("load",             107,   l.nx(), l.ny(), l.cw, l.ch,  "Load net from SVG")
1553                 # unfolding enthusiasts - try uncommenting this
1554                 self.ancestors = Draw.Number("depth", 654,        l.nx(), l.ny(), cw, ch, self.ancestors.val, 0, 9999,  "depth of branching 0=diffuse")
1555                 #self.noise = Draw.Number("noise", 631,        l.nx(), l.ny(), cw, ch, self.noise.val, 0.0, 1.0,  "noisyness of branching")
1556                 #Draw.Button("UnfoldAll",           714, l.nx(), l.ny(), l.cw, l.ch, "Unfold all meshes and save their nets")
1557                 options = "order %t|random %x0|brightest %x1|curvature %x2|winding %x3| 1010 %x4|largest %x5"
1558                 self.shape = Draw.Menu(options, 713,  l.nx(), l.ny(), cw, ch, self.shape.val, "shape of net")
1559                 Draw.Button("exit",         6,   l.nx(), l.ny(), l.cw, l.ch, "exit")
1560                 BGL.glClearColor(0.5, 0.5, 0.5, 1)
1561                 BGL.glColor3f(0.3,0.3,0.3)
1562                 l.newLine()
1563                 BGL.glRasterPos2i(32, 100)
1564                 Draw.Text(self.message)
1565
1566 class FlowLayout:
1567         def __init__(self, margin, cw, ch, w, h):
1568                 self.x = margin-cw-4
1569                 self.y = margin
1570                 self.cw = cw
1571                 self.ch = ch
1572                 self.width = w
1573                 self.height = h
1574                 self.margin = margin
1575         def nx(self):
1576                 self.x+=(self.cw+4)
1577                 if(self.x>self.width):
1578                         self.x = self.margin
1579                         self.y-=self.ch+4
1580                 return self.x
1581         def ny(self):
1582                 return self.y
1583         def newLine(self):
1584                 self.y-=self.ch+self.margin
1585                 self.x = self.margin
1586
1587 try:
1588         sys.setrecursionlimit(10000)
1589         gui = GUI()
1590         gui.makeStandardGUI()
1591         #gui.makePopupGUI()
1592 except:
1593         traceback.print_exc(file=sys.stdout)