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