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