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