6
Tip: 'Unfold meshes to create nets'
8
Author: Matthew Chadwick
12
from Blender.Mathutils import *
22
import xml.sax, xml.sax.handler, xml.sax.saxutils
25
print "One of the Python modules required can't be found."
26
print sys.exc_info()[1]
27
traceback.print_exc(file=sys.stdout)
29
__author__ = 'Matthew Chadwick'
30
__version__ = '2.2.4 24032007'
31
__url__ = ["http://celeriac.net/unfolder/", "blender", "blenderartist"]
32
__email__ = ["post at cele[remove this text]riac.net", "scripts"]
37
Unfolds the selected mesh onto a plane to form a net
39
Not all meshes can be unfolded
41
Meshes must be free of holes,
42
isolated edges (not part of a face), twisted quads and other rubbish.
43
Nice clean triangulated meshes unfold best
45
This program is free software; you can distribute it and/or modify it under the terms
46
of the GNU General Public License as published by the Free Software Foundation; version 2
47
or later, currently at http://www.gnu.org/copyleft/gpl.html
49
The idea came while I was riding a bike.
55
def __init__(self, mesh):
57
# straight from the documentation
58
self.edgeFaces = dict([(edge.key, []) for edge in mesh.edges])
59
for face in mesh.faces:
61
for key in face.edge_keys:
62
self.edgeFaces[key].append(face)
63
def findTakenAdjacentFace(self, bface, edge):
64
return self.findAdjacentFace(bface, edge)
65
# find the first untaken (non-selected) adjacent face in the list of adjacent faces for the given edge
66
def findAdjacentFace(self, bface, edge):
67
faces = self.edgeFaces[edge.key()]
68
for i in xrange(len(faces)):
70
j = (i+1) % len(faces)
71
while(faces[j]!=bface):
72
if faces[j].sel == False:
74
j = (j+1) % len(faces)
76
def returnFace(self, face):
81
def takeAdjacentFace(self, bface, edge):
84
face = self.findAdjacentFace(bface, edge)
89
def takeFace(self, bface):
95
class IntersectionResult:
96
def __init__(self, rn, rd, v=None):
100
def intersected(self):
101
return not(not(self.v))
102
def isParallel(self):
104
def isColinear(self):
106
def intersection(self):
109
# represents a line segment between two points [p1, p2]. the points are [x,y]
111
def __init__(self, p):
113
def intersects(self, s):
114
rn = ((self.p[0].y-s.p[0].y)*(s.p[1].x-s.p[0].x)-(self.p[0].x-s.p[0].x)*(s.p[1].y-s.p[0].y))
115
rd = ((self.p[1].x-self.p[0].x)*(s.p[1].y-s.p[0].y)-(self.p[1].y-self.p[0].y)*(s.p[1].x-s.p[0].x))
116
# need an epsilon closeTo() here
117
if(rd<0.0000001 or rn==0.0):
118
return IntersectionResult(rn,rd)
120
s = ((self.p[0].y-s.p[0].y)*(self.p[1].x-self.p[0].x)-(self.p[0].x-s.p[0].x)*(self.p[1].y-self.p[0].y)) / rd
121
i = (0.0<=r and r<=1.0 and 0.0<=s and s<=1.0)
124
ix = self.p[0].x + r*(self.p[1].x - self.p[0].x)
125
iy = self.p[0].y + r*(self.p[1].y - self.p[0].y)
127
if ( abs(ix-self.p[0].x)>t and abs(iy-self.p[0].x)>t and abs(ix-self.p[1].x)>t and abs(iy-self.p[1].y)>t ):
128
return IntersectionResult( rn, rd,Vector([ix,iy,0.0]))
133
def __init__(self, face):
135
def segmentAt(self, i):
136
if(i>self.face.nPoints()-1):
138
if(i==self.face.nPoints()-1):
142
return LineSegment([ self.face.v[i], self.face.v[j] ])
143
def iterateSegments(self, something):
145
for i in xrange(self.face.nPoints()):
146
results.extend(something.haveSegment(self.segmentAt(i)))
148
def compareSegments(self, something, segment):
150
for i in xrange(self.face.nPoints()):
151
results.append(something.compareSegments([self.segmentAt(i), segment]))
154
class FaceOverlapTest:
155
def __init__(self, face1, face2):
156
self.faces = [face1, face2]
157
self.segments = [ LineSegments(self.faces[0]), LineSegments(self.faces[1]) ]
158
def suspectsOverlap(self):
159
tests = self.segments[0].iterateSegments(self)
162
if( i!=None and i.intersected() ):
165
def haveSegment(self, segment):
166
return self.segments[1].compareSegments(self, segment)
167
def compareSegments(self, segments):
168
return segments[0].intersects(segments[1])
172
# A fold between two faces with a common edge
175
def __init__(self, parent, refPoly, poly, edge, angle=None):
178
self.refPoly = refPoly
183
self.foldedEdge = edge
188
self.refPolyNormal = refPoly.normal()
189
self.polyNormal = poly.normal()
191
self.angle = self.calculateAngle()
192
self.foldingPoly = poly.rotated(edge, self.angle)
195
self.foldingPoly = poly
196
self.unfoldedEdge = self.edge
197
self.unfoldedNormal = None
198
self.animAngle = self.angle
200
self.nancestors = None
202
self.foldingPoly = self.poly.rotated(self.edge, self.dihedralAngle())
208
if(self.nancestors==None):
209
self.nancestors = self.computeAncestors()
210
return self.nancestors
211
def computeAncestors(self):
212
if(self.parent==None):
215
return self.parent.ancestors()+1
216
def dihedralAngle(self):
218
def unfoldTo(self, f):
219
self.animAngle = self.angle*f
220
self.foldingPoly = self.poly.rotated(self.edge, self.animAngle)
221
def calculateAngle(self):
222
sangle = Mathutils.AngleBetweenVecs(self.refPolyNormal, self.polyNormal)
225
ncp = Mathutils.CrossVecs(self.refPolyNormal, self.polyNormal)
226
dp = Mathutils.DotVecs(ncp, self.edge.vector)
231
def alignWithParent(self):
233
def unfoldedNormal(self):
234
return self.unfoldedNormal
240
return Poly.fromVectors([self.edge.v1, self.edge.v2, Vector([0,0,0])])
241
def unfoldedFace(self):
242
return self.foldingPoly
244
if(self.parent!=None):
245
self.parent.foldFace(self)
246
def foldFace(self, child):
247
child.foldingPoly.rotate(self.edge, self.animAngle)
248
if(self.parent!=None):
249
self.parent.foldFace(child)
254
# Trees build folds by traversing the mesh according to a local measure
256
def __init__(self, net, parent,fold,otherConstructor=None):
259
self.face = fold.srcFace
260
self.poly = Poly.fromBlenderFace(self.face)
261
self.generations = net.generations
266
if not(otherConstructor):
267
self.edges = net.edgeIteratorClass(self)
269
return self.edges.goodness()
270
def compare(self, other):
271
if(self.goodness() > other.goodness()):
280
self.tooLong = self.fold.ancestors()>self.generations
281
if(self.edges.hasNext() and self.growing):
282
edge = self.edges.next()
283
tface = self.net.facesAndEdges.takeAdjacentFace(self.face, edge)
285
self.branch(tface, edge)
286
if(self.parent==None):
293
return (self.parent!=None and self.parent.grown)
300
def branch(self, tface, edge):
301
fold = Fold(self.fold, self.poly, Poly.fromBlenderFace(tface), edge)
303
self.net.myFacesVisited+=1
304
tree = Tree(self.net, self, fold)
307
overlaps = self.net.checkOverlaps(fold)
309
self.net.overlaps+=nc
310
if(nc>0 and self.net.avoidsOverlaps):
311
self.handleOverlap(fold, overlaps)
314
def handleOverlap(self, fold, overlaps):
315
self.net.facesAndEdges.returnFace(fold.srcFace)
316
self.net.myFacesVisited-=1
317
for cfold in overlaps:
321
def addFace(self, fold):
322
ff = fold.unfoldedFace()
323
fold.desFace = self.net.addFace(ff, fold.srcFace)
324
self.net.folds.append(fold)
325
self.net.addBranch(fold.tree)
326
fold.tree.growing = not(self.tooLong)
327
if(self.net.diffuse==False):
330
# A Net is the result of the traversal of the mesh by Trees
332
def __init__(self, src, des):
335
self.firstFace = None
336
self.firstPoly = None
338
self.edgeIteratorClass = RandomEdgeIterator
340
self.srcFaces = src.faces
341
self.facesAndEdges = FacesAndEdges(self.src)
342
self.myFacesVisited = 0
348
self.avoidsOverlaps = True
351
self.firstFaceIndex = None
354
self.perFoldIPO = None
356
self.generations = 128
359
self.grownBranches = 0
360
self.assignsUV = True
361
self.animates = False
362
self.showProgress = False
364
def setSelectedFaces(self, faces):
365
self.srcFaces = faces
366
self.facesAndEdges = FacesAndEdges(self.srcFaces)
367
def setShowProgress(self, show):
368
self.showProgress = show
369
# this method really needs work
371
selectedFaces = [face for face in self.src.faces if (self.src.faceUV and face.sel)]
372
if(self.avoidsOverlaps):
373
print "unfolding with overlap detection"
374
if(self.firstFaceIndex==None):
375
self.firstFaceIndex = random.randint(0, len(self.src.faces)-1)
377
print "Using user-selected seed face ", self.firstFaceIndex
378
self.firstFace = self.src.faces[self.firstFaceIndex]
379
z = min([v.co.z for v in self.src.verts])-0.1
380
ff = Poly.fromBlenderFace(self.firstFace)
382
raise Exception("This mesh contains an isolated edge - it must consist only of faces")
383
testFace = Poly.fromVectors( [ Vector([0.0,0.0,0.0]), Vector([0.0,1.0,0.0]), Vector([1.0,1.0,0.0]) ] )
388
if ff.v[u].x==ff.v[u+1].x and ff.v[u].y==ff.v[u+1].y:
392
# here we make a couple of folds, not part of the net, which serve to get the net into the xy plane
393
xyFace = Poly.fromList( [ [ff.v[u].x,ff.v[u].y, z] , [ff.v[v].x,ff.v[v].y, z] , [ff.v[w].x+0.1,ff.v[w].y+0.1, z] ] )
394
refFace = Poly.fromVectors([ ff.v[u], ff.v[v], xyFace.v[1], xyFace.v[0] ] )
395
xyFold = Fold(None, xyFace, refFace, Edge(xyFace.v[0], xyFace.v[1] ))
396
self.refFold = Fold(xyFold, refFace, ff, Edge(refFace.v[0], refFace.v[1] ))
397
self.refFold.srcFace = self.firstFace
398
trunk = Tree(self, None, self.refFold)
399
trunk.generations = self.generations
401
self.facesAndEdges.takeFace(self.firstFace)
402
self.myFacesVisited+=1
403
self.refFold.unfold()
404
self.refFold.tree = trunk
405
self.refFold.desFace = self.addFace(self.refFold.unfoldedFace(), self.refFold.srcFace)
406
self.folds.append(self.refFold)
409
while(self.myFacesVisited<len(self.src.faces) and len(self.branches) > 0):
410
if self.edgeIteratorClass==RandomEdgeIterator:
411
i = random.randint(0,len(self.branches)-1)
412
tree = self.branches[i]
421
i = (i + 1) % len(self.branches)
423
for face in self.src.faces:
425
for face in selectedFaces:
430
for fold in self.folds:
431
self.assignUV(fold.srcFace, fold.unfoldedFace())
432
print " assigned uv to ", len(self.folds), len(self.src.faces)
434
def checkOverlaps(self, fold):
435
#return self.getOverlapsBetween(fold, self.folds)
436
return self.getOverlapsBetweenGL(fold, self.folds)
437
def getOverlapsBetween(self, fold, folds):
438
if(fold.parent==None):
440
mf = fold.unfoldedFace()
443
mdf = afold.unfoldedFace()
445
it1 = FaceOverlapTest(mf, mdf)
446
it2 = FaceOverlapTest(mdf, mf)
447
overlap = (it1.suspectsOverlap() or it2.suspectsOverlap())
448
inside = ( mdf.containsAnyOf(mf) or mf.containsAnyOf(mdf) )
449
if( overlap or inside or mdf.overlays(mf)):
452
def getOverlapsBetweenGL(self, fold, folds):
453
b = fold.unfoldedFace().bounds()
454
polys = len(folds)*4+16 # the buffer is nhits, mindepth, maxdepth, name
455
buffer = BGL.Buffer(BGL.GL_INT, polys)
456
BGL.glSelectBuffer(polys, buffer)
457
BGL.glRenderMode(BGL.GL_SELECT)
461
BGL.glMatrixMode(BGL.GL_PROJECTION)
463
BGL.glOrtho(b[0].x, b[1].x, b[1].y, b[0].y, 0.0, 10.0)
464
#clip = BGL.Buffer(BGL.GL_FLOAT, 4)
465
#clip.list = [0,0,0,0]
466
#BGL.glClipPlane(BGL.GL_CLIP_PLANE1, clip)
467
# could use clipping planes here too
468
BGL.glMatrixMode(BGL.GL_MODELVIEW)
470
bx = (b[1].x - b[0].x)
471
by = (b[1].y - b[0].y)
474
for f in xrange(len(folds)):
478
BGL.glBegin(BGL.GL_LINE_LOOP)
479
for v in afold.unfoldedFace().v:
480
BGL.glVertex2f(v.x, v.y)
484
hits = BGL.glRenderMode(BGL.GL_RENDER)
485
buffer = [buffer[i] for i in xrange(3, 4*hits, 4)]
486
o = [folds[buffer[i]] for i in xrange(len(buffer))]
487
return self.getOverlapsBetween(fold, o)
488
def colourFace(self, face, cr):
495
def setAvoidsOverlaps(self, avoids):
496
self.avoidsOverlaps = avoids
497
def addBranch(self, branch):
498
self.branches.append(branch)
499
if self.edgeIteratorClass!=RandomEdgeIterator:
500
self.branches.sort(lambda b1, b2: b1.compare(b2))
502
return len(self.src.faces)
504
return len(self.branches)
505
def facesCreated(self):
506
return len(self.des.faces)
507
def facesVisited(self):
508
return self.myFacesVisited
509
def getOverlaps(self):
511
def sortOutIPOSource(self):
512
print "Sorting out IPO"
513
if self.foldIPO!=None:
517
o = Blender.Object.Get("FoldRate")
519
o = Blender.Object.New("Empty", "FoldRate")
520
Blender.Scene.GetCurrent().objects.link(o)
521
if(o.getIpo()==None):
522
ipo = Blender.Ipo.New("Object", "FoldRateIPO")
523
z = ipo.addCurve("RotZ")
524
print " added RotZ IPO curve"
526
# again, why is this 10x out ?
527
z.addBezier((180, self.ff/10.0))
528
z.addBezier((361, 0.0))
531
z.setInterpolation("Bezier")
532
z.setExtrapolation("Cyclic")
534
print " added IPO source"
535
def setIPOSource(self, object):
537
self.foldIPO = object
538
for i in xrange(self.foldIPO.getIpo().getNcurves()):
539
self.IPOCurves[self.foldIPO.getIpo().getCurves()[i].getName()] = i
540
print " added ", self.foldIPO.getIpo().getCurves()[i].getName()
542
print "Problem setting IPO object"
543
print sys.exc_info()[1]
544
traceback.print_exc(file=sys.stdout)
545
def setFoldFactor(self, ff):
548
for fold in self.folds:
549
if(fold.getParent()!=None):
550
print fold.getID(), fold.dihedralAngle(), fold.getParent().getID()
552
p = int(float(self.myFacesVisited)/float(len(self.src.faces)) * 100)
553
print str(p) + "% unfolded"
554
print "faces created:", self.facesCreated()
555
print "faces visited:", self.facesVisited()
556
print "originalfaces:", len(self.src.faces)
558
if(self.avoidsOverlaps):
559
print "net avoided at least ", self.getOverlaps(), " overlaps ",
560
n = len(self.src.faces) - self.facesCreated()
562
print "but was unable to avoid ", n, " overlaps. Incomplete net."
564
print "- A complete net."
566
print "net has at least ", self.getOverlaps(), " collision(s)"
568
# fold all my folds to a fraction of their total fold angle
569
def unfoldToCurrentFrame(self):
570
self.unfoldTo(Blender.Scene.GetCurrent().getRenderingContext().currentFrame())
571
def unfoldTo(self, frame):
572
frames = Blender.Scene.GetCurrent().getRenderingContext().endFrame()
573
if(self.foldIPO!=None and self.foldIPO.getIpo()!=None):
574
f = self.foldIPO.getIpo().EvaluateCurveOn(self.IPOCurves["RotZ"],frame)
575
# err, this number seems to be 10x less than it ought to be
576
fff = 1.0 - (f*10.0 / self.ff)
578
fff = 1.0-((frame)/(frames*1.0))
579
for fold in self.folds:
581
for fold in self.folds:
583
tface = fold.unfoldedFace()
586
for v in bface.verts:
587
v.co.x = tface.v[i].x
588
v.co.y = tface.v[i].y
589
v.co.z = tface.v[i].z
591
Window.Redraw(Window.Types.VIEW3D)
593
def addFace(self, poly, originalFace=None):
594
originalLength = len(self.des.verts)
595
self.des.verts.extend([Vector(vv.x, vv.y, vv.z) for vv in poly.v])
596
self.des.faces.extend([ range(originalLength, originalLength + poly.size()) ])
597
newFace = self.des.faces[len(self.des.faces)-1]
598
newFace.uv = [vv for vv in poly.v]
599
if(originalFace!=None and self.src.vertexColors):
600
newFace.col = [c for c in originalFace.col]
601
if(self.feedback!=None):
602
pu = str(int(self.fractionUnfolded() * 100))+"% unfolded"
603
howMuchDone = str(self.myFacesVisited)+" of "+str(len(self.src.faces))+" "+pu
604
self.feedback.say(howMuchDone)
605
#Window.DrawProgressBar (p, pu)
606
if(self.showProgress):
607
Window.Redraw(Window.Types.VIEW3D)
609
def fractionUnfolded(self):
610
return float(self.myFacesVisited)/float(len(self.src.faces))
611
def assignUV(self, face, uv):
612
face.uv = [Vector(v.x, v.y) for v in uv.v]
613
def unfoldAll(feedback=None):
614
objects = Blender.Object.Get()
615
for object in objects:
616
if(object.getType()=='Mesh' and not(object.getName().endswith("_net")) and len(object.getData(False, True).faces)>1):
617
net = Net.createNet(object, feedback)
618
net.searchForUnfolding()
619
svg = SVGExporter(net, object.getName()+".svg")
621
unfoldAll = staticmethod(unfoldAll)
622
def searchForUnfolding(self, limit=-1):
625
while(overlaps > 0 or attempts<limit):
627
overlaps = self.report()
630
def unfoldSelected(feedback=None, netName=None):
631
return Net.createNet(Blender.Object.GetSelected()[0], feedback, netName)
632
unfoldSelected = staticmethod(unfoldSelected)
633
def clone(self, object=None):
636
net = Net.createNet(object, self.feedback)
637
net.avoidsOverlaps = net.avoidsOverlaps
639
def createNet(ob, feedback=None, netName=None):
640
mesh = ob.getData(mesh=1)
643
netName = ob.name[0:16]+"_net"
645
netObject = Blender.Object.Get(netName)
646
netMesh = netObject.getData(False, True)
648
netMesh.verts = None # clear the mesh
650
netObject = Blender.Object.New("Mesh", netName)
653
netObject = Blender.Object.New("Mesh", netName)
654
netMesh = netObject.getData(False, True) # True means "as a Mesh not an NMesh"
656
Blender.Scene.GetCurrent().objects.link(netObject)
660
netMesh.materials = mesh.materials
661
netMesh.vertexColors = True
663
print "Problem setting materials here"
664
net = Net(mesh, netMesh)
665
if mesh.faceUV and mesh.activeFace>=0 and (mesh.faces[mesh.activeFace].sel):
666
net.firstFaceIndex = mesh.activeFace
668
net.feedback = feedback
670
createNet = staticmethod(createNet)
671
def importNet(filename):
672
netName = filename.rstrip(".svg").replace("\\","/")
673
netName = netName[netName.rfind("/")+1:]
675
netObject = Blender.Object.Get(netName)
677
netObject = Blender.Object.New("Mesh", netName)
678
netObject.getData(mesh=1).name = netName
680
Blender.Scene.GetCurrent().objects.link(netObject)
683
net = Net(None, netObject.getData(mesh=1))
684
handler = NetHandler(net)
685
xml.sax.parse(filename, handler)
686
Window.Redraw(Window.Types.VIEW3D)
688
importNet = staticmethod(importNet)
689
def getSourceMesh(self):
692
# determines the order in which to visit faces according to a local measure
694
def __init__(self, branch, otherConstructor=None):
696
self.bface = branch.getFace()
697
self.edge = branch.getFold().getEdge()
698
self.net = branch.getNet()
699
self.n = len(self.bface)
704
self.computeGoodness()
705
if(otherConstructor==None):
707
def createEdges(self):
709
e = Edge.edgesOfBlenderFace(self.net.getSourceMesh(), self.bface)
711
if not(edge.isBlenderSeam() and edge!=self.edge):
712
self.edges.append(edge)
713
def sequenceEdges(self):
716
edge = self.edges[self.i]
720
return len(self.edges)
724
return (self.i<len(self.edges))
726
return self.gooodness
727
def computeGoodness(self):
730
self.edges.append(self.edges.pop(0))
732
class RandomEdgeIterator(EdgeIterator):
733
def sequenceEdges(self):
735
random.shuffle(self.edges)
737
return random.randint(0, self.net.srcSize())
740
class Largest(EdgeIterator):
741
def sequenceEdges(self):
743
f = self.net.facesAndEdges.findAdjacentFace(self.bface, e)
745
e.setGoodness(f.area)
746
self.edges.sort(lambda e1, e2: -e1.compare(e2))
747
def computeGoodness(self):
748
self.gooodness = self.bface.area
751
class Brightest(EdgeIterator):
752
def sequenceEdges(self):
753
for edge in self.edges:
754
f = self.net.facesAndEdges.findAdjacentFace(self.bface, edge)
757
if self.net.src.vertexColors:
760
rc = float(random.randint(0, self.net.srcSize())) / float(self.net.srcSize()) / 100.0
763
self.edges.sort(lambda e1, e2: e1.compare(e2))
764
def computeGoodness(self):
766
if self.net.src.vertexColors:
767
for c in self.bface.col:
771
class OddEven(EdgeIterator):
773
def sequenceEdges(self):
774
OddEven.i = not(OddEven.i)
778
class Curvature(EdgeIterator):
779
def sequenceEdges(self):
780
p1 = Poly.fromBlenderFace(self.bface)
782
for edge in self.edges:
783
f = self.net.facesAndEdges.findAdjacentFace(self.bface, edge)
785
p2 = Poly.fromBlenderFace(f)
786
fold = Fold(None, p1, p2, edge)
788
b = Tree(self.net, self.branch, fold, self)
789
c = Curvature(b, False)
793
self.edges.sort(lambda e1, e2: e1.compare(e2))
794
tg = (self.gooodness + gg)
795
rc = float(random.randint(0, self.net.srcSize())) / float(self.net.srcSize()) / 100.0
797
self.gooodness = self.gooodness + rc / tg
798
def computeGoodness(self):
800
for edge in self.edges:
801
f = self.net.facesAndEdges.findAdjacentFace(self.bface, edge)
803
p1 = Poly.fromBlenderFace(self.bface)
804
p2 = Poly.fromBlenderFace(f)
805
f = Fold(None, p1, p2, edge)
806
g += f.dihedralAngle()
811
def __init__(self, v1=None, v2=None, mEdge=None, i=-1):
817
self.v1 = mEdge.v1.co.copy()
818
self.v2 = mEdge.v2.co.copy()
820
self.vector = self.v1-self.v2
821
self.vector.resize3D()
822
self.vector.normalize()
825
def fromBlenderFace(mesh, bface, i):
832
edge = Edge( bface.v[i].co.copy(), bface.v[j].co.copy() )
833
edge.bEdge = mesh.findEdge(bface.v[i], bface.v[j])
836
fromBlenderFace=staticmethod(fromBlenderFace)
837
def edgesOfBlenderFace(mesh, bmFace):
838
edges = [mesh.edges[mesh.findEdges(edge[0], edge[1])] for edge in bmFace.edge_keys]
843
for j in xrange(1, len(bmFace)+1):
844
vj = v[j%len(bmFace)]
846
if((ee.v1.index==vi.index and ee.v2.index==vj.index) or (ee.v2.index==vi.index and ee.v1.index==vj.index)):
847
e.append(Edge(vi.co, vj.co, ee, i))
851
edgesOfBlenderFace=staticmethod(edgesOfBlenderFace)
852
def isBlenderSeam(self):
853
return (self.bmEdge.flag & Mesh.EdgeFlags.SEAM)
855
return (self.bmEdge.flag & Mesh.EdgeFlags.FGON)
856
def mapTo(self, poly):
857
if(self.idx==len(poly.v)-1):
861
return Edge(poly.v[self.idx], poly.v[j])
862
def isDegenerate(self):
863
return self.vector.length==0
865
return [ [s.v1.x, s.v1.y, s.v1.z], [s.v2.x, s.v2.y,s.v2.z] ]
867
return self.bmEdge.key
869
return self.gooodness
870
def setGoodness(self, g):
872
def compare(self, other):
873
if(self.goodness() > other.goodness()):
895
return CrossVecs(p,q)
899
if(vv.x!=vv.x or vv.y!=vv.y or vv.z!=vv.z): # Nan check
910
return [ x/n, y/n, z/n ]
911
def centerAtOrigin(self):
914
toOrigin = TranslationMatrix(mp)
915
self.v = [(vv * toOrigin) for vv in self.v]
917
mv = TranslationMatrix(tv)
918
self.v = [(vv * mv) for vv in self.v]
920
mp = Vector(self.midpoint())
921
fromOrigin = TranslationMatrix(mp)
923
toOrigin = TranslationMatrix(mp)
924
sm = ScaleMatrix(s, 4)
925
# Todo, the 3 lines below in 1 LC
926
self.v = [(vv * toOrigin) for vv in self.v]
927
self.v = [(sm * vv) for vv in self.v]
928
self.v = [(vv * fromOrigin) for vv in self.v]
933
def rotated(self, axis, angle):
935
p.rotate(axis, angle)
937
def rotate(self, axis, angle):
938
rotation = RotationMatrix(angle, 4, "r", axis.vector)
939
toOrigin = TranslationMatrix(axis.v1n)
940
fromOrigin = TranslationMatrix(axis.v1)
941
# Todo, the 3 lines below in 1 LC
942
self.v = [(vv * toOrigin) for vv in self.v]
943
self.v = [(rotation * vv) for vv in self.v]
944
self.v = [(vv * fromOrigin) for vv in self.v]
945
def moveAlong(self, vector, distance):
946
t = TranslationMatrix(vector)
947
s = ScaleMatrix(distance, 4)
949
self.v = [(vv * ts) for vv in self.v]
951
if(self.boundz == None):
952
vv = [vv for vv in self.v]
953
vv.sort(key=lambda v: v.x)
955
maxx = vv[len(vv)-1].x
956
vv.sort(key=lambda v: v.y)
958
maxy = vv[len(vv)-1].y
959
self.boundz = [Vector(minx, miny, 0), Vector(maxx, maxy, 0)]
961
def fromBlenderFace(bface):
964
vec = Vector([vv.co[0], vv.co[1], vv.co[2] , 1.0])
967
fromBlenderFace = staticmethod(fromBlenderFace)
971
vec = Vector( [vvv for vvv in vv] )
975
fromList = staticmethod(fromList)
976
def fromVectors(vectors):
978
p.v.extend([v.copy().resize4D() for v in vectors])
980
fromVectors = staticmethod(fromVectors)
985
def hasVertex(self, ttv):
986
v = Mathutils.Vector(ttv)
989
vv = Mathutils.Vector(tv)
992
if abs(vv.x-v.x)<t and abs(vv.y-v.y)<t:
995
def overlays(self, poly):
996
if len(poly.v)!=len(self.v):
1000
if self.hasVertex(point):
1002
return c==len(self.v)
1003
def sharesVertexWith(self, poly):
1004
for point in poly.v:
1005
if(self.hasVertex(point)):
1008
def containsAnyOf(self, poly):
1009
for point in poly.v:
1010
if(not(self.hasVertex(point))):
1011
if self.contains(point):
1016
# This is the BEST algorithm for point-in-polygon detection.
1017
# It's by W. Randolph Franklin. It's also very beautiful (looks even better in C).
1018
# All the others are shite; they give false positives.
1019
# returns 1 for inside, 1 or 0 for edges
1020
def contains(self, tp):
1023
for i in xrange(len(self.v)):
1027
if ((((cv.y<=tp.y) and (tp.y<nv.y)) or ((nv.y<=tp.y) and (tp.y<cv.y))) and (tp.x < (nv.x - cv.x) * (tp.y - cv.y) / (nv.y - cv.y) + cv.x)):
1032
def __init__(self, net, filename):
1034
print self.net.des.name
1035
self.object = self.net.object
1036
print "Exporting ", self.object
1037
self.filename = filename
1043
print "Exporting SVG to ", self.filename
1044
self.file = open(self.filename, 'w')
1045
self.e = xml.sax.saxutils.XMLGenerator(self.file, "UTF-8")
1047
atts["width"] = "100%"
1048
atts["height"] = "100%"
1049
atts["viewBox"] = str(self.vxmin)+" "+str(self.vymin)+" "+str(self.vxmax-self.vxmin)+" "+str(self.vymax-self.vymin)
1050
atts["xmlns:nets"] = "http://celeriac.net/unfolder/rdf#"
1051
atts["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
1052
atts["xmlns"] ="http://www.w3.org/2000/svg"
1053
a = xml.sax.xmlreader.AttributesImpl(atts)
1054
self.e.startDocument()
1055
self.e.startElement("svg", a)
1056
self.e.startElement("defs", xml.sax.xmlreader.AttributesImpl({}))
1058
atts["type"]="text/css"
1059
self.e.startElement("style", atts)
1060
# can't find a proper way to do this
1061
self.file.write("<![CDATA[")
1062
self.file.write("polygon.poly{fill:white;stroke:black;stroke-width: 0.001}")
1063
self.file.write("g#foldLines line.valley{stroke:white;stroke-width:0.01;stroke-dasharray:0.02,0.01,0.02,0.05}")
1064
self.file.write("g#foldLines line.mountain{stroke:white;stroke-width:0.01;stroke-dasharray:0.02,0.04}")
1065
self.file.write("]]>")
1066
self.e.endElement("style")
1067
self.e.endElement("defs")
1071
self.e.startElement("metadata", xml.sax.xmlreader.AttributesImpl({}))
1072
self.e.startElement("nets:net", xml.sax.xmlreader.AttributesImpl({}))
1073
for i in xrange(1, len(self.net.folds)):
1074
fold = self.net.folds[i]
1075
# AttributesNSImpl - documentation is rubbish. using this hack.
1077
atts["nets:id"] = "fold"+str(fold.getID())
1078
if(fold.parent!=None):
1079
atts["nets:parent"] = "fold"+str(fold.parent.getID())
1081
atts["nets:parent"] = "null"
1082
atts["nets:da"] = str(fold.dihedralAngle())
1083
if(fold.parent!=None):
1084
atts["nets:ofPoly"] = "poly"+str(fold.parent.foldingPoly.getID())
1086
atts["nets:ofPoly"] = ""
1087
atts["nets:toPoly"] = "poly"+str(fold.foldingPoly.getID())
1088
a = xml.sax.xmlreader.AttributesImpl(atts)
1089
self.e.startElement("nets:fold", a)
1090
self.e.endElement("nets:fold")
1091
self.e.endElement("nets:net")
1092
self.e.endElement("metadata")
1094
self.e.endElement("svg")
1095
self.e.endDocument()
1098
self.net.unfoldTo(1)
1099
bb = self.object.getBoundBox()
1100
self.vxmin = bb[0][0]
1101
self.vymin = bb[0][1]
1102
self.vxmax = bb[7][0]
1103
self.vymax = bb[7][1]
1106
atts["id"] = self.object.getName()
1107
a = xml.sax.xmlreader.AttributesImpl(atts)
1108
self.e.startElement("g", a)
1113
self.e.endElement("g")
1115
def addClipPath(self):
1117
atts["id"] = "netClip"
1118
atts["clipPathUnits"] = "userSpaceOnUse"
1119
atts["x"] = str(self.vxmin)
1120
atts["y"] = str(self.vymin)
1121
atts["width"] = "100%"
1122
atts["height"] = "100%"
1123
self.e.startElement("clipPath", atts)
1125
self.e.endElement("clipPath")
1126
def addUVImage(self):
1127
image = Blender.Image.GetCurrent()
1130
ifn = image.getFilename()
1131
#ifn = self.filename.replace(".svg", ".jpg")
1132
#image.setFilename(ifn)
1133
#ifn = ifn[ifn.rfind("/")+1:]
1136
atts["clip-path"] = "url(#netClip)"
1137
atts["xlink:href"] = ifn
1138
self.e.startElement("image", atts)
1139
self.e.endElement("image")
1142
atts["id"] = "polys"
1143
a = xml.sax.xmlreader.AttributesImpl(atts)
1144
self.e.startElement("g", a)
1145
for i in xrange(len(self.net.folds)):
1146
self.addPoly(self.net.folds[i])
1147
self.e.endElement("g")
1148
def addFoldLines(self):
1150
atts["id"] = "foldLines"
1151
a = xml.sax.xmlreader.AttributesImpl(atts)
1152
self.e.startElement("g", a)
1153
for i in xrange( 1, len(self.net.folds)):
1154
self.addFoldLine(self.net.folds[i])
1155
self.e.endElement("g")
1156
def addFoldLine(self, fold):
1157
edge = fold.edge.mapTo(fold.parent.foldingPoly)
1158
if fold.dihedralAngle()>0:
1163
atts["x1"] = str(edge.v1.x)
1164
atts["y1"] = str(edge.v1.y)
1165
atts["x2"] = str(edge.v2.x)
1166
atts["y2"] = str(edge.v2.y)
1167
atts["id"] = "fold"+str(fold.getID())
1168
atts["class"] = foldType
1169
a = xml.sax.xmlreader.AttributesImpl(atts)
1170
self.e.startElement("line", a)
1171
self.e.endElement("line")
1172
def addCutLines(self):
1174
atts["id"] = "cutLines"
1175
a = xml.sax.xmlreader.AttributesImpl(atts)
1176
self.e.startElement("g", a)
1177
for i in xrange( 1, len(self.net.cuts)):
1178
self.addCutLine(self.net.cuts[i])
1179
self.e.endElement("g")
1180
def addCutLine(self, cut):
1181
edge = cut.edge.mapTo(cut.parent.foldingPoly)
1182
if cut.dihedralAngle()>0:
1187
atts["x1"] = str(edge.v1.x)
1188
atts["y1"] = str(edge.v1.y)
1189
atts["x2"] = str(edge.v2.x)
1190
atts["y2"] = str(edge.v2.y)
1191
atts["id"] = "cut"+str(cut.getID())
1192
atts["class"] = foldType
1193
a = xml.sax.xmlreader.AttributesImpl(atts)
1194
self.e.startElement("line", a)
1195
self.e.endElement("line")
1196
def addPoly(self, fold):
1197
face = fold.foldingPoly
1199
if fold.desFace.col:
1200
col = fold.desFace.col[0]
1201
rgb = "rgb("+str(col.r)+","+str(col.g)+","+str(col.b)+")"
1203
atts["class"] = "poly"
1204
atts["id"] = "poly"+str(face.getID())
1214
atts["points"] = points
1215
a = xml.sax.xmlreader.AttributesImpl(atts)
1216
self.e.startElement("polygon", a)
1217
self.e.endElement("polygon")
1218
def fileSelected(filename):
1220
net = Registry.GetKey('unfolder')['net']
1221
exporter = SVGExporter(net, filename)
1224
print "Problem exporting SVG"
1225
traceback.print_exc(file=sys.stdout)
1226
fileSelected = staticmethod(fileSelected)
1229
class NetHandler(xml.sax.handler.ContentHandler):
1230
def __init__(self, net):
1232
self.first = (41==41)
1233
self.currentElement = None
1235
self.currentAction = None
1236
self.foldsPending = {}
1239
self.actions["nets:fold"] = self.foldInfo
1240
self.actions["line"] = self.cutOrFold
1241
self.actions["polygon"] = self.createPoly
1242
def setDocumentLocator(self, locator):
1244
def startDocument(self):
1246
def endDocument(self):
1247
for fold in self.foldsPending.values():
1248
face = self.net.addFace(fold.unfoldedFace())
1250
self.net.folds.append(fold)
1251
self.net.addFace(self.first)
1252
self.foldsPending = None
1254
def startPrefixMapping(self, prefix, uri):
1256
def endPrefixMapping(self, prefix):
1258
def startElement(self, name, attributes):
1259
self.currentAction = None
1261
self.currentAction = self.actions[name]
1264
if(self.currentAction!=None):
1265
self.currentAction(attributes)
1266
def endElement(self, name):
1268
def startElementNS(self, name, qname, attrs):
1269
self.currentAction = self.actions[name]
1270
if(self.currentAction!=None):
1271
self.currentAction(attributes)
1272
def endElementNS(self, name, qname):
1274
def characters(self, content):
1276
def ignorableWhitespace(self):
1278
def processingInstruction(self, target, data):
1280
def skippedEntity(self, name):
1282
def foldInfo(self, atts):
1283
self.foldsPending[atts["nets:id"]] = atts
1284
def createPoly(self, atts):
1285
xy = re.split('[, ]' , atts["points"])
1287
for i in xrange(0, len(xy)-1, 2):
1288
v = Vector([float(xy[i]), float(xy[i+1]), 0.0])
1290
poly = Poly.fromVectors(vectors)
1291
if(self.first==True):
1293
self.polys[atts["id"]] = poly
1294
def cutOrFold(self, atts):
1297
fi = self.foldsPending[fid]
1300
p1 = Vector([float(atts["x1"]), float(atts["y1"]), 0.0])
1301
p2 = Vector([float(atts["x2"]), float(atts["y2"]), 0.0])
1307
parent = self.foldsPending[fi["nets:parent"]]
1311
ofPoly = self.polys[fi["nets:ofPoly"]]
1315
toPoly = self.polys[fi["nets:toPoly"]]
1318
fold = Fold(parent, ofPoly , toPoly, edge, float(fi["nets:da"]))
1319
self.foldsPending[fid] = fold
1320
def fileSelected(filename):
1322
net = Net.importNet(filename)
1324
Registry.GetKey('unfolder')['net'] = net
1326
Registry.SetKey('unfolder', {})
1327
Registry.GetKey('unfolder')['net'] = net
1328
Registry.GetKey('unfolder')['lastpath'] = filename
1330
print "Problem importing SVG"
1331
traceback.print_exc(file=sys.stdout)
1332
fileSelected = staticmethod(fileSelected)
1337
self.overlaps = Draw.Create(0)
1338
self.ani = Draw.Create(0)
1339
self.selectedFaces =0
1340
self.search = Draw.Create(0)
1342
self.ancestors = Draw.Create(0)
1343
self.noise = Draw.Create(0.0)
1344
self.shape = Draw.Create(0)
1345
self.nOverlaps = 1==2
1346
self.iterators = [RandomEdgeIterator,Brightest,Curvature,EdgeIterator,OddEven,Largest]
1347
self.iterator = RandomEdgeIterator
1348
self.overlapsText = "*"
1350
def makePopupGUI(self):
1351
useRandom = Draw.Create(0)
1353
pub.append(("Search", self.search, "Search for non-overlapping net (maybe forever)"))
1354
pub.append(("Random", useRandom, "Random style net"))
1357
ok = Blender.Draw.PupBlock("Unfold", pub)
1360
self.iterator = RandomEdgeIterator
1362
self.iterator = Curvature
1364
def makeStandardGUI(self):
1365
Draw.Register(self.draw, self.keyOrMouseEvent, self.buttonEvent)
1366
def installScriptLink(self):
1367
print "Adding script link for animation"
1368
s = Blender.Scene.GetCurrent().getScriptLinks("FrameChanged")
1369
if(s!=None and s.count("frameChanged.py")>0):
1372
script = Blender.Text.Get("frameChanged.py")
1374
script = Blender.Text.New("frameChanged.py")
1375
script.write("import Blender\n")
1376
script.write("import mesh_unfolder as Unfolder\n")
1377
script.write("u = Blender.Registry.GetKey('unfolder')\n")
1378
script.write("if u!=None:\n")
1379
script.write("\tn = u['net']\n")
1380
script.write("\tif(n!=None and n.animates):\n")
1381
script.write("\t\tn.unfoldToCurrentFrame()\n")
1382
Blender.Scene.GetCurrent().addScriptLink("frameChanged.py", "FrameChanged")
1384
anc = self.ancestors.val
1394
self.say("Unfolding...")
1396
while(s):# and search < searchLimit):
1399
net = Net.unfoldSelected(self, name)
1400
net.setAvoidsOverlaps(not(self.overlaps.val))
1402
print "Unfolding selected object"
1403
net.edgeIteratorClass = self.iterator
1404
print "Using ", net.edgeIteratorClass
1405
net.animates = self.ani.val
1406
self.diffuse = (self.ancestors.val==0)
1407
net.diffuse = self.diffuse
1408
net.generations = self.ancestors.val
1409
net.noise = self.noise.val
1410
print "even:", net.diffuse, " depth:", net.generations
1415
t = "Overlaps>="+str(n)
1417
t = "A complete net."
1418
self.nOverlaps = (n>=1)
1420
self.say(self.message+" - unfolding failed - try again ")
1421
elif(not(self.overlaps.val)):
1422
self.say("Success. Complete net - no overlaps ")
1424
self.say("Unfolding complete")
1425
self.ancestors.val = anc
1426
s = (self.search.val and n>=1.0)
1427
dict = Registry.GetKey('unfolder')
1431
Registry.SetKey('unfolder', dict)
1436
self.say("Please select an object to unfold")
1438
self.say("Problem unfolding selected object - see console for details")
1439
print "Problem unfolding selected object:"
1440
print sys.exc_info()[1]
1441
traceback.print_exc(file=sys.stdout)
1443
if Registry.GetKey('unfolder')==None:
1446
Registry.GetKey('unfolder')['net'].sortOutIPOSource()
1447
self.installScriptLink()
1449
def keyOrMouseEvent(self, evt, val):
1450
if (evt == Draw.ESCKEY and not val):
1452
def buttonEvent(self, evt):
1457
Registry.GetKey('unfolder')['net'].setAvoidsOverlaps(self.overlaps.val)
1461
print "Trying to set IPO curve"
1463
s = Blender.Object.GetSelected()
1465
Registry.GetKey('unfolder')['net'].setIPOSource( s[0] )
1466
print "Set IPO curve"
1468
print "Please select an object to use the IPO of"
1470
print "Problem setting IPO source"
1476
if (Registry.GetKey('unfolder')['net']!=None):
1477
Registry.GetKey('unfolder')['net'].animates = self.ani.val
1479
Registry.GetKey('unfolder')['net'].sortOutIPOSource()
1480
self.installScriptLink()
1482
print sys.exc_info()[1]
1483
traceback.print_exc(file=sys.stdout)
1489
if (Registry.GetKey('unfolder')['net']!=None):
1490
Registry.GetKey('unfolder')['net'].assignUVs()
1491
self.say("Assigned UVs")
1493
print sys.exc_info()[1]
1494
traceback.print_exc(file=sys.stdout)
1497
if( testOverlap() == True):
1506
self.iterator = self.iterators[self.shape.val]
1509
if( testContains() == True):
1516
filename = "net.svg"
1517
s = Blender.Object.GetSelected()
1518
if(s!=None and len(s)>0):
1519
filename = s[0].getName()+".svg"
1521
if (Registry.GetKey('unfolder')['net']!=None):
1522
filename = Registry.GetKey('unfolder')['net'].des.name
1526
filename=filename+".svg"
1527
Window.FileSelector(SVGExporter.fileSelected, "Select filename", filename)
1529
print "Problem exporting SVG"
1530
traceback.print_exc(file=sys.stdout)
1533
Window.FileSelector(NetHandler.fileSelected, "Select file")
1535
print "Problem importing SVG"
1536
traceback.print_exc(file=sys.stdout)
1540
Window.Redraw(Window.Types.SCRIPT)
1544
l = FlowLayout(32, cw, ch, 350, 64)
1546
self.search = Draw.Toggle("search", 19, l.nx(), l.ny(), l.cw, l.ch, self.search.val, "Search for non-overlapping mesh (potentially indefinitely)")
1547
self.overlaps = Draw.Toggle("overlaps", 5, l.nx(), l.ny(), l.cw, l.ch, self.overlaps.val, "Allow overlaps / avoid overlaps - if off, will not place overlapping faces")
1548
self.ani = Draw.Toggle("ani", 7, l.nx(), l.ny(), l.cw, l.ch, self.ani.val, "Animate net")
1549
Draw.Button("uv", 87, l.nx(), l.ny(), l.cw, l.ch, "Assign net as UV to source mesh (overwriting existing UV)")
1550
Draw.Button("Unfold", 1, l.nx(), l.ny(), l.cw, l.ch, "Unfold selected mesh to net")
1551
Draw.Button("save", 104, l.nx(), l.ny(), l.cw, l.ch, "Save net as SVG")
1552
Draw.Button("load", 107, l.nx(), l.ny(), l.cw, l.ch, "Load net from SVG")
1553
# unfolding enthusiasts - try uncommenting this
1554
self.ancestors = Draw.Number("depth", 654, l.nx(), l.ny(), cw, ch, self.ancestors.val, 0, 9999, "depth of branching 0=diffuse")
1555
#self.noise = Draw.Number("noise", 631, l.nx(), l.ny(), cw, ch, self.noise.val, 0.0, 1.0, "noisyness of branching")
1556
#Draw.Button("UnfoldAll", 714, l.nx(), l.ny(), l.cw, l.ch, "Unfold all meshes and save their nets")
1557
options = "order %t|random %x0|brightest %x1|curvature %x2|winding %x3| 1010 %x4|largest %x5"
1558
self.shape = Draw.Menu(options, 713, l.nx(), l.ny(), cw, ch, self.shape.val, "shape of net")
1559
Draw.Button("exit", 6, l.nx(), l.ny(), l.cw, l.ch, "exit")
1560
BGL.glClearColor(0.5, 0.5, 0.5, 1)
1561
BGL.glColor3f(0.3,0.3,0.3)
1563
BGL.glRasterPos2i(32, 100)
1564
Draw.Text(self.message)
1567
def __init__(self, margin, cw, ch, w, h):
1568
self.x = margin-cw-4
1574
self.margin = margin
1577
if(self.x>self.width):
1578
self.x = self.margin
1584
self.y-=self.ch+self.margin
1585
self.x = self.margin
1588
sys.setrecursionlimit(10000)
1590
gui.makeStandardGUI()
1593
traceback.print_exc(file=sys.stdout)