saving and loading of settings per tree, also added a way to get unique animation...
[blender.git] / release / scripts / wizard_curve2tree.py
1 #!BPY
2
3 """
4 Name: 'Tree from Curves'
5 Blender: 245
6 Group: 'Wizards'
7 Tip: 'Generate trees from curve shapes'
8 """
9
10 __author__ = "Campbell Barton"
11 __url__ = ['www.blender.org', 'blenderartists.org']
12 __version__ = "0.1"
13
14 __bpydoc__ = """\
15
16 """
17
18 import bpy
19 import Blender
20 from Blender.Mathutils import Vector, CrossVecs, AngleBetweenVecs, LineIntersect, TranslationMatrix, ScaleMatrix
21 from Blender.Geometry import ClosestPointOnLine
22
23 def debug_pt(co):
24         Blender.Window.SetCursorPos(tuple(co))
25         Blender.Window.RedrawAll()
26         print 'debugging', co
27
28
29 def closestVecIndex(vec, vecls):
30         best= -1
31         best_dist = 100000000
32         for i, vec_test in enumerate(vecls):
33                 dist = (vec-vec_test).length
34                 if dist < best_dist:
35                         best = i
36                         best_dist = dist
37         
38         return best
39
40 eul = 0.00001
41
42 class tree:
43         def __init__(self):
44                 self.branches_all =             []
45                 self.branches_root =    []
46                 self.mesh = None
47                 self.armature = None
48                 self.object = None
49                 self.limbScale = 1.0
50                 
51                 self.debug_objects = []
52         
53         def __repr__(self):
54                 s = ''
55                 s += '[Tree]'
56                 s += '  limbScale: %.6f' % self.limbScale
57                 s += '  object: %s' % self.object
58                 
59                 for brch in self.branches_root:
60                         s += str(brch)
61                 return s
62         
63         
64         def fromCurve(self, object):
65                 self.object = object
66                 curve = object.data
67                 
68                 # Set the curve object scale
69                 if curve.bevob:
70                         # A bit of a hack to guess the size of the curve object if you have one.
71                         bb = curve.bevob.boundingBox
72                         # self.limbScale = (bb[0] - bb[7]).length / 2.825 # THIS IS GOOD WHEN NON SUBSURRFED
73                         self.limbScale = (bb[0] - bb[7]).length / 1.8
74                 # end
75                 
76                 
77                 # Get the curve points as bpoints
78                 for spline in curve:
79                         brch = branch()
80                         self.branches_all.append(brch)
81                         
82                         for bez in spline:
83                                 # calc normal vector later
84                                 pt = bpoint(brch, Vector(bez.vec[1]), Vector(), bez.radius * self.limbScale)
85                                 brch.bpoints.append( pt )
86                 
87                 
88                 # Get the curve as a mesh. - for inbetween points
89                 tmpme = bpy.data.meshes.new()   
90                 
91                 # remove/backup bevel ob
92                 bev_back = curve.bevob
93                 if bev_back: curve.bevob = None
94                 
95                 # get the curve mesh data
96                 tmpob = bpy.data.scenes.active.objects.new( curve )
97                 tmpme.getFromObject(object)
98                 bpy.data.scenes.active.objects.unlink(tmpob)
99                 
100                 # restore bevel ob
101                 if bev_back:
102                         curve.bevob = bev_back
103                         
104                         # Guess the size of the curve object if you have one. This is not perfect but good enough
105                         bb = bev_back.boundingBox
106                         self.limbScale = (bb[0] - bb[7]).length / 2.825
107                         
108                         
109                 
110                 # TEMP FOR TESTING
111                 # bpy.data.scenes.active.objects.new(tmpme)
112                 
113                 vecs = [ v.co for v in tmpme.verts ]
114                 del tmpme
115                 
116                 # for branch
117                 #used_points = set()
118                 for brch in self.branches_all:
119                         offset = 0
120                         for i in xrange(1, len(brch.bpoints)):
121                                 # find the start/end points
122                                 start_pt =      brch.bpoints[offset+i-1]
123                                 end_pt =        brch.bpoints[offset+i]
124                                 
125                                 start = end = None
126                                 for j, co in enumerate(vecs):
127                                         if start == None:
128                                                 if (co-start_pt.co).length < eul:
129                                                         start = j
130                                         if end == None:
131                                                 if (co-end_pt.co).length < eul:
132                                                         end = j
133                                         if start != None and end != None:
134                                                 break
135                                 
136                                 # for now we assuem the start is always a lower index.
137                                 #if start > end:
138                                 #       raise "error index is not one we like"
139                                 
140                                 #used_points.add(start)
141                                 #used_points.add(end)
142                                 radius = start_pt.radius
143                                 
144                                 #print 'coords', start_co, end_co
145                                 #### print "starting", start, end
146                                 if start > end:
147                                         j = start-1
148                                         raise "some bug!"
149                                 else:
150                                         j = start+1
151                                 
152                                 step = 1
153                                 step_tot = abs(start-end)
154                                 while j!=end:
155                                         #radius = (start_pt.radius*(step_tot-step) - end_pt.radius*step ) / step_tot
156                                         w1 = step_tot-step
157                                         w2 = step
158                                         
159                                         radius = ((start_pt.radius*w1) + (end_pt.radius*w2)) / step_tot
160                                         
161                                         #### print i,j, radius
162                                         pt = bpoint(brch, Vector(vecs[j]), Vector(), radius)
163                                         brch.bpoints.insert(offset+i, pt)
164                                         offset+=1
165                                         
166                                         if start > end:
167                                                 j-=1
168                                         else:
169                                                 j+=1
170                                         
171                                         step +=1
172                 
173                 # Now calculate the normals
174                 for brch in self.branches_all:
175                         for i in xrange(1, len(brch.bpoints)-1):
176                                 brch.bpoints[i].next = brch.bpoints[i+1]
177                                 brch.bpoints[i].prev = brch.bpoints[i-1]
178                         
179                         brch.bpoints[0].next = brch.bpoints[1]  
180                         brch.bpoints[-1].prev = brch.bpoints[-2]
181                         
182                         
183                         for pt in brch.bpoints:
184                                 pt.calcNormal()
185                                 pt.calcNextMidCo()
186                 
187                 # remove segments
188                 # We may want to remove segments for 2 reasons
189                 # 1) - too high resolution
190                 # 2) - too close together (makes yucky geometry)
191                 
192         def resetTags(self, value):
193                 for brch in self.branches_all:
194                         brch.tag = value
195         
196         def buildConnections(self, sloppy=1.0, base_trim = 1.0):
197                 '''
198                 build tree data - fromCurve must run first
199                 
200                 '''
201                 
202                 # Connect branches
203                 for i in xrange(len(self.branches_all)):
204                         brch_i = self.branches_all[i]
205                         
206                         for j in xrange(len(self.branches_all)):
207                                 if i != j:
208                                         # See if any of the points match this branch
209                                         # see if Branch 'i' is the child of branch 'j'
210                                         
211                                         brch_j = self.branches_all[j]
212                                         
213                                         best_j, dist = brch_j.findClosest(brch_i.bpoints[0].co)
214                                         
215                                         # Check its in range, allow for a bit out - hense the 1.5
216                                         if dist < best_j.radius * sloppy:
217                                                 
218                                                 # if 1) dont remove the whole branch, maybe an option but later
219                                                 # if 2) we are alredy a parent, cant remove me now.... darn :/ not nice... could do this properly but it would be slower and its a corner case.
220                                                 # if 3) this point is within the branch, remove it.
221                                                 while   len(brch_i.bpoints)>2 and\
222                                                                 brch_i.bpoints[0].isParent == False and\
223                                                                 (brch_i.bpoints[0].co - best_j.nextMidCo).length < best_j.radius * base_trim:
224                                                         
225                                                         # brch_i.bpoints[0].next = 101 # testing.
226                                                         del brch_i.bpoints[0]
227                                                         brch_i.bpoints[0].prev = None
228                                                 
229                                                 brch_i.parent_pt = best_j
230                                                 best_j.isParent = True # dont remove me
231                                                 
232                                                 # addas a member of best_j.children later when we have the geometry info available.
233                                                 
234                                                 #### print "Found Connection!!!", i, j
235                                                 break # go onto the next branch
236                 
237                 """
238                         children = [brch_child for brch_child in pt.children]
239                         if children:
240                                 # This pt is one side of the segment, pt.next joins this segment.
241                                 # calculate the median point the 2 segments would span
242                                 # Once this is done we need to adjust 2 things
243                                 # 1) move both segments up/down so they match the branches best.
244                                 # 2) set the spacing of the segments around the point.
245                                 
246                 
247                 # First try to get the ideal some space around each joint
248                 # the spacing shoule be an average of 
249                 for brch.bpoints:
250                 """
251                 
252                 '''
253                 for brch in self.branches_all:
254                         brch.checkPointList()
255                 '''
256                 
257                 # Calc points with dependancies
258                 # detect circular loops!!! - TODO
259                 self.resetTags(False)
260                 done_nothing = False
261                 while done_nothing == False:
262                         done_nothing = True
263                         
264                         for brch in self.branches_all:
265                                 
266                                 if brch.tag == False and (brch.parent_pt == None or brch.parent_pt.branch.tag == True):
267                                         # Assign this to a spesific side of the parents point
268                                         # we know this is a child but not which side it should be attached to.
269                                         if brch.parent_pt:
270                                                 child_locs = [\
271                                                 brch.parent_pt.childPoint(0),\
272                                                 brch.parent_pt.childPoint(1),\
273                                                 brch.parent_pt.childPoint(2),\
274                                                 brch.parent_pt.childPoint(3)]
275                                                 
276                                                 best_idx = closestVecIndex(brch.bpoints[0].co, child_locs)
277                                                 brch.parent_pt.children[best_idx] = brch
278                                         # DONE
279                                         
280                                         done_nothing = False
281                                         
282                                         for pt in brch.bpoints:
283                                                 # for temp debugging
284                                                 ## self.mesh.faces.extend([pt.verts])
285                                                 pt.calcVerts()
286                                                 
287                                                 # pt.toMesh(self.mesh) # Cant do here because our children arnt calculated yet!
288                                         
289                                         brch.tag = True
290         
291         def optimizeSpacing(self, density=1.0, joint_compression=1.0, joint_smooth=1.0):
292                 '''
293                 Optimize spacing, taking branch hierarchy children into account,
294                 can add or subdivide segments so branch joins dont look horrible.
295                 '''
296                 for brch in self.branches_all:
297                         brch.evenJointDistrobution(joint_compression)
298                 
299                 # Correct points that were messed up from sliding
300                 # This happens when one point is pushed past another and the branch gets an overlaping line
301                 for brch in self.branches_all:
302                         brch.fixOverlapError(joint_smooth)
303                 
304                 # Collapsing
305                 for brch in self.branches_all:
306                         brch.collapsePoints(density, joint_smooth)
307                 
308                 for brch in self.branches_all:
309                         brch.branchReJoin()
310         
311         
312         def toDebugDisplay(self):
313                 '''
314                 Should be able to call this at any time to see whats going on
315                 '''
316                 sce = bpy.data.scenes.active
317                 
318                 for ob in self.debug_objects:
319                         for ob in sce.objects:
320                                 sce.objects.unlink(ob)
321                 
322                 for branch_index, brch in enumerate(self.branches_all):
323                         pt_index = 0
324                         for pt_index, pt in enumerate(brch.bpoints):
325                                 name = '%.3d_%.3d' % (branch_index, pt_index) 
326                                 if pt.next==None:
327                                         name += '_end'
328                                 if pt.prev==None:
329                                         name += '_start'
330                                         
331                                 ob = sce.objects.new('Empty', name)
332                                 self.debug_objects.append(ob)
333                                 mat = ScaleMatrix(pt.radius, 4) * TranslationMatrix(pt.co)
334                                 ob.setMatrix(mat)
335                                 ob.setDrawMode(8) # drawname
336                 Blender.Window.RedrawAll()
337                 
338                 
339         
340         def toMesh(self, mesh=None, do_uvmap=True, do_uv_scalewidth=True, uv_image = None):
341                 # Simple points
342                 '''
343                 self.mesh = bpy.data.meshes.new()
344                 self.object = bpy.data.scenes.active.objects.new(self.mesh)
345                 self.mesh.verts.extend([ pt.co for brch in self.branches_all for pt in brch.bpoints ])
346                 '''
347                 if mesh:
348                         self.mesh = mesh
349                         mesh.verts = None
350                 else:
351                         self.mesh = bpy.data.meshes.new()
352                 
353                 totverts = 0
354                 
355                 for brch in self.branches_all:
356                         totverts += len(brch.bpoints)
357                 
358                 self.mesh.verts.extend( [ (0.0,0.0,0.0) ] * ((totverts * 4)+1) ) # +1 is a dummy vert
359                 verts = self.mesh.verts
360                 
361                 # Assign verts to points, 4 verts for each point.
362                 i = 1 # dummy vert, should be 0
363                 for brch in self.branches_all:                  
364                         for pt in brch.bpoints:
365                                 pt.verts[0] = verts[i]
366                                 pt.verts[1] = verts[i+1]
367                                 pt.verts[2] = verts[i+2]
368                                 pt.verts[3] = verts[i+3]
369                                 i+=4
370                                 
371                         # Do this again because of collapsing
372                         # pt.calcVerts(brch)
373                 
374                 # roll the tube so quads best meet up to their branches.
375                 for brch in self.branches_all:
376                         #for pt in brch.bpoints:
377                         if brch.parent_pt:
378                                 
379                                 # Use temp lists for gathering an average
380                                 if brch.parent_pt.roll_angle == None:
381                                         brch.parent_pt.roll_angle = [brch.getParentQuadAngle()]
382                                 # More then 2 branches use this point, add to the list
383                                 else:
384                                         brch.parent_pt.roll_angle.append( brch.getParentQuadAngle() )
385                 
386                 # average the temp lists into floats
387                 for brch in self.branches_all:
388                         #for pt in brch.bpoints:
389                         if brch.parent_pt and type(brch.parent_pt.roll_angle) == list:
390                                 # print brch.parent_pt.roll_angle
391                                 f = 0.0
392                                 for val in brch.parent_pt.roll_angle:
393                                         f += val
394                                 brch.parent_pt.roll_angle = f/len(brch.parent_pt.roll_angle)
395                 
396                 # set the roll of all the first segments that have parents,
397                 # this is because their roll is set from their parent quad and we dont want them to roll away from that.
398                 for brch in self.branches_all:
399                         if brch.parent_pt:
400                                 # if the first joint has a child then apply half the roll
401                                 # theres no correct solition here, but this seems ok
402                                 if brch.bpoints[0].roll_angle != None:
403                                         #brch.bpoints[0].roll_angle *= 0.5
404                                         #brch.bpoints[0].roll_angle = 0.0
405                                         #brch.bpoints[1].roll_angle = 0.0
406                                         brch.bpoints[0].roll_angle = 0
407                                         pass
408                                 else:
409                                         # our roll was set from the branches parent and needs no changing
410                                         # set it to zero so the following functions know to interpolate.
411                                         brch.bpoints[0].roll_angle = 25.0
412                                         #brch.bpoints[1].roll_angle = 0.0
413                 
414                 '''
415                 Now interpolate the roll!
416                 The method used here is a little odd.
417                 
418                 * first loop up the branch and set each points value to the "last defined" value and record the steps
419                 since the last defined value
420                 * Do the same again but backwards
421                 
422                 now for each undefined value we have 1 or 2 values, if its 1 its simple we just use that value 
423                 ( no interpolation ), if there are 2 then we use the offsets from each end to work out the interpolation.
424                 
425                 one up, one back, and another to set the values, so 3 loops all up.
426                 '''
427                 #### print "scan up the branch..."
428                 for brch in self.branches_all:
429                         last_value = None
430                         last_index = -1
431                         for i in xrange(len(brch.bpoints)):
432                                 pt = brch.bpoints[i]
433                                 if type(pt.roll_angle) in (float, int):
434                                         last_value = pt.roll_angle
435                                         last_index = i
436                                 else:
437                                         if type(last_value) in (float, int):
438                                                 # Assign a list, because there may be a connecting roll value from another joint
439                                                 pt.roll_angle = [(last_value, i-last_index)]
440                                 
441                         #### print "scan down the branch..."
442                         last_value = None
443                         last_index = -1
444                         for i in xrange(len(brch.bpoints)-1, -1, -1): # same as above but reverse
445                                 pt = brch.bpoints[i]
446                                 if type(pt.roll_angle) in (float, int):
447                                         last_value = pt.roll_angle
448                                         last_index = i
449                                 else:
450                                         if last_value != None:
451                                                 if type(pt.roll_angle) == list:
452                                                         pt.roll_angle.append((last_value, last_index-i))
453                                                 else:
454                                                         #pt.roll_angle = [(last_value, last_index-i)]
455                                                         
456                                                         # Dont bother assigning a list because we wont need to add to it later
457                                                         pt.roll_angle = last_value 
458                         
459                         # print "looping ,...."
460                         ### print "assigning/interpolating roll values"
461                         for pt in brch.bpoints:
462                                 
463                                 # print "this roll IS", pt.roll_angle
464                                 
465                                 if pt.roll_angle == None:
466                                         continue
467                                 elif type(pt.roll_angle) in (float, int):
468                                         pass
469                                 elif len(pt.roll_angle) == 1:
470                                         pt.roll_angle = pt.roll_angle[0][0]
471                                 else:
472                                         # interpolate
473                                         tot = pt.roll_angle[0][1] + pt.roll_angle[1][1]
474                                         pt.roll_angle = \
475                                          (pt.roll_angle[0][0] * (tot - pt.roll_angle[0][1]) +\
476                                           pt.roll_angle[1][0] * (tot - pt.roll_angle[1][1])) / tot
477                                         
478                                         #### print pt.roll_angle, 'interpolated roll'
479                                         
480                                 pt.roll(pt.roll_angle)
481                                 
482                 # Done with temp average list. now we know the best roll for each branch.
483                 
484                 # mesh the data
485                 for brch in self.branches_all:
486                         for pt in brch.bpoints:
487                                 pt.toMesh(self.mesh)
488                 
489                 faces = self.mesh.faces
490                 faces.extend([ face for brch in self.branches_all for pt in brch.bpoints for face in pt.faces if face ])
491                 
492                 if do_uvmap:
493                         # Assign the faces back
494                         face_index = 0
495                         for brch in self.branches_all:
496                                 for pt in brch.bpoints:
497                                         for i in (0,1,2,3):
498                                                 if pt.faces[i]:
499                                                         pt.faces[i] = faces[face_index]
500                                                         pt.faces[i].smooth = True
501                                                         face_index +=1
502                         
503                         self.mesh.faceUV = True
504                         for brch in self.branches_all:
505                                 
506                                 y_val = 0.0
507                                 for pt in brch.bpoints:
508                                         if pt.next:
509                                                 y_size = (pt.co-pt.next.co).length
510                                                 
511                                                 # scale the uvs by the radiusm, avoids stritching.
512                                                 if do_uv_scalewidth:
513                                                         y_size = y_size / pt.radius
514                                                 
515                                                 for i in (0,1,2,3):
516                                                         if pt.faces[i]:
517                                                                 if uv_image:
518                                                                         pt.faces[i].image = uv_image
519                                                                 uvs = pt.faces[i].uv
520                                                                 uvs[3].x = i
521                                                                 uvs[3].y = y_val+y_size
522                                                                 
523                                                                 uvs[0].x = i
524                                                                 uvs[0].y = y_val
525                                                                 
526                                                                 uvs[1].x = i+1
527                                                                 uvs[1].y = y_val
528                                                                 
529                                                                 uvs[2].x = i+1
530                                                                 uvs[2].y = y_val+y_size
531                                                                 
532                                                 
533                                                 do_uv_scalewidth
534                                                 if pt.next:     
535                                                         y_val += y_size
536                 else:
537                         # no UV's
538                         for f in self.mesh.faces:
539                                 f.smooth = True
540                 
541                 return self.mesh
542
543         def toArmature(self, ob_arm, armature):
544                 
545                 armature.drawType = Blender.Armature.STICK
546                 armature.makeEditable() # enter editmode
547                 
548                 # Assume toMesh has run
549                 self.armature = armature
550                 for bonename in armature.bones.keys():
551                         del armature.bones[bonename]
552                 
553                 
554                 group_names = []
555                 
556                 for i, brch in enumerate(self.branches_all):
557                         
558                         # get a list of parent points to make into bones. use parents and endpoints
559                         bpoints_parent = [pt for pt in brch.bpoints if pt.isParent or pt.prev == None or pt.next == None]
560                         bpbone_last = None
561                         for j in xrange(len(bpoints_parent)-1):
562                                 
563                                 # bone container class
564                                 bpoints_parent[j].bpbone = bpbone = bpoint_bone()
565                                 bpbone.name = '%i_%i' % (i,j) # must be unique
566                                 group_names.append(bpbone.name)
567                                 
568                                 bpbone.editbone = Blender.Armature.Editbone() # automatically added to the armature
569                                 self.armature.bones[bpbone.name] = bpbone.editbone
570                                 
571                                 bpbone.editbone.head = bpoints_parent[j].co
572                                 bpbone.editbone.head = bpoints_parent[j].co
573                                 bpbone.editbone.tail = bpoints_parent[j+1].co
574                                 
575                                 # parent the chain.
576                                 if bpbone_last:
577                                         bpbone.editbone.parent = bpbone_last.editbone
578                                         bpbone.editbone.options = [Blender.Armature.CONNECTED]
579                                 
580                                 bpbone_last = bpbone
581                 
582                 for brch in self.branches_all:
583                         if brch.parent_pt: # We must have a parent
584                                 
585                                 # find the bone in the parent chain to use for the parent of this
586                                 parent_pt = brch.parent_pt
587                                 bpbone_parent = None
588                                 while parent_pt:
589                                         bpbone_parent = parent_pt.bpbone
590                                         if bpbone_parent:
591                                                 break
592                                         
593                                         parent_pt = parent_pt.prev
594                                 
595                                 
596                                 if bpbone_parent:
597                                         brch.bpoints[0].bpbone.editbone.parent = bpbone_parent.editbone
598                                 else: # in rare cases this may not work. should be verry rare but check anyway.
599                                         print 'this is really odd... look into the bug.'
600                 
601                 self.armature.update() # exit editmode
602                 
603                 # Skin the mesh
604                 if self.mesh:
605                         for group in group_names:
606                                 self.mesh.addVertGroup(group)
607                 
608                 for brch in self.branches_all:
609                         vertList = []
610                         group = '' # dummy
611                         
612                         for pt in brch.bpoints:
613                                 if pt.bpbone:
614                                         if vertList:
615                                                 self.mesh.assignVertsToGroup(group, vertList, 1.0, Blender.Mesh.AssignModes.ADD)
616                                         
617                                         vertList = []
618                                         group = pt.bpbone.name
619                                 
620                                 vertList.extend( [v.index for v in pt.verts] )
621                         
622                         if vertList:
623                                 self.mesh.assignVertsToGroup(group, vertList, 1.0, Blender.Mesh.AssignModes.ADD)
624                 
625                 return self.armature
626         
627         def toAction(self, ob_arm, texture, anim_speed=1.0, anim_magnitude=1.0, anim_speed_size_scale=True, anim_offset_scale=1.0):
628                 # Assume armature
629                 action = ob_arm.action
630                 if not action:
631                         action = bpy.data.actions.new()
632                         action.fakeUser = False # so we dont get masses of bad data
633                         ob_arm.action = action
634                 
635                 # Blender.Armature.NLA.ob_arm.
636                 pose = ob_arm.getPose()
637                 
638                 for pose_bone in pose.bones.values():
639                         pose_bone.insertKey(ob_arm, 0, [Blender.Object.Pose.ROT], True)
640                 
641                 # Now get all the IPO's
642                 
643                 ipo_dict = action.getAllChannelIpos()
644                 # print ipo_dict
645                 
646                 # Sicne its per frame, it increases very fast. scale it down a bit
647                 anim_speed = anim_speed/10
648                 
649                 # When we have the same trees next to eachother, they will animate the same way unless we give each its own texture or offset settings.
650                 # We can use the object's location as a factor - this also will have the advantage? of seeing the animation move across the tree's
651                 # allow a scale so the difference between tree textures can be adjusted.
652                 anim_offset = self.object.matrixWorld.translationPart() * anim_offset_scale
653                 
654                 anim_speed_final = anim_speed
655                 # Assign drivers to them all
656                 for name, ipo in ipo_dict.iteritems():
657                         tex_str = 'b.Texture.Get("%s")' % texture.name
658                         
659                         if anim_speed_size_scale:
660                                 # Adjust the speed by the bone size.
661                                 # get the point from the name. a bit ugly but works fine ;) - Just dont mess the index up!
662                                 lookup = [int(val) for val in name.split('_')]
663                                 pt = self.branches_all[ lookup[0] ].bpoints[ lookup[1] ]
664                                 anim_speed_final = anim_speed / (1+pt.radius)
665                         
666                         #for cu in ipo:
667                         #       #cu.delBezier(0) 
668                         #       #cu.driver = 2 # Python expression
669                         #       #cu.driverExpression = 'b.Get("curframe")/100.0'
670                         cu = ipo[Blender.Ipo.PO_QUATX]
671                         cu.delBezier(0) 
672                         cu.driver = 2 # Python expression
673                         cu.driverExpression = '%.3f*(%s.evaluate(((b.Get("curframe")*%.3f)+%.3f,%.3f,%.3f)).w-0.5)' % (anim_magnitude, tex_str, anim_speed_final, anim_offset.x, anim_offset.y, anim_offset.z)
674                         
675                         cu = ipo[Blender.Ipo.PO_QUATY]
676                         cu.delBezier(0) 
677                         cu.driver = 2 # Python expression
678                         cu.driverExpression = '%.3f*(%s.evaluate((%.3f,(b.Get("curframe")*%.3f)+%.3f,%.3f)).w-0.5)' % (anim_magnitude, tex_str,  anim_offset.x, anim_speed_final, anim_offset.y, anim_offset.z)
679                         
680                         cu = ipo[Blender.Ipo.PO_QUATZ]
681                         cu.delBezier(0) 
682                         cu.driver = 2 # Python expression
683                         cu.driverExpression = '%.3f*(%s.evaluate(%.3f,%.3f,(b.Get("curframe")*%.3f)+%.3f).w-0.5)' % (anim_magnitude, tex_str,  anim_offset.x, anim_offset.y, anim_speed_final, anim_offset.z)
684                         
685                         
686                         #(%s.evaluate((b.Get("curframe")*%.3f,0,0)).w-0.5)*%.3f
687                 
688                 
689 zup = Vector(0,0,1)
690
691 class bpoint_bone:
692         def __init__(self):
693                 self.name = None
694                 self.editbone = None
695                 self.blenbone = None
696                 self.posebone = None
697
698 class bpoint(object):
699         ''' The point in the middle of the branch, not the mesh points
700         '''
701         __slots__ = 'branch', 'co', 'no', 'radius', 'vecs', 'verts', 'children', 'faces', 'next', 'prev', 'isParent', 'bpbone', 'roll_angle', 'nextMidCo', 'childrenMidCo', 'childrenMidRadius', 'targetCos'
702         def __init__(self, brch, co, no, radius):
703                 self.branch = brch
704                 self.co = co
705                 self.no = no
706                 self.radius = radius
707                 self.vecs =             [None, None, None, None] # 4 for now
708                 self.verts =    [None, None, None, None]
709                 self.children = [None, None, None, None] # child branches, dont fill in faces here
710                 self.faces = [None, None, None, None]
711                 self.next = None
712                 self.prev = None
713                 self.isParent = False
714                 self.bpbone = None # bpoint_bone instance
715                 
716                 # when set, This is the angle we need to roll to best face our branches
717                 # the roll that is set may be interpolated if we are between 2 branches that need to roll.
718                 # Set to None means that the roll will be left default (from parent)
719                 self.roll_angle = None
720                 
721                 
722                 # The location between this and the next point,
723                 # if we want to be tricky we can try make this not just a simple
724                 # inbetween and use the normals to have some curvature
725                 self.nextMidCo = None
726                 
727                 # Similar to above, median point of all children
728                 self.childrenMidCo = None
729                 
730                 # Similar as above, but for radius
731                 self.childrenMidRadius = None
732                 
733                 # Target locations are used when you want to move the point to a new location but there are
734                 # more then 1 influence, build up a list and then apply
735                 self.targetCos = []
736         
737         def __repr__(self):
738                 s = ''
739                 s += '\t\tco:', self.co
740                 s += '\t\tno:', self.no
741                 s += '\t\tradius:', self.radius
742                 s += '\t\tchildren:', [(child != False) for child in self.children]
743                 return s
744                 
745                 
746         
747         def setCo(self, co):
748                 self.co[:] = co
749                 self.calcNextMidCo()
750                 self.calcNormal()
751                 
752                 if self.prev:
753                         self.prev.calcNextMidCo()
754                         self.prev.calcNormal()
755                         self.prev.calcChildrenMidData()
756                 
757                 if self.next:
758                         self.prev.calcNormal()
759                 
760                 self.calcChildrenMidData()
761                 
762                 
763         def nextLength(self):
764                 return (self.co-self.next.co).length
765         def prevLength(self):
766                 return (self.co-self.prev.co).length
767                 
768         def hasOverlapError(self):
769                 if self.prev == None:
770                         return False
771                 if self.next == None:
772                         return False
773                 '''
774                 # see if this point sits on the line between its siblings.
775                 co, fac = ClosestPointOnLine(self.co, self.prev.co, self.next.co)
776                 
777                 if fac >= 0.0 and fac <= 1.0:
778                         return False # no overlap, we are good
779                 else:
780                         return True # error, some overlap
781                 '''
782                 
783                 
784                 # Alternate method, maybe better
785                 ln = self.nextLength()
786                 lp = self.prevLength()
787                 ls = (self.prev.co-self.next.co).length
788                 
789                 # Are we overlapping? the length from our next or prev is longer then the next-TO-previous?
790                 if ln>ls or lp>ls:
791                         return True
792                 else:
793                         return False
794                 
795         
796         def applyTargetLocation(self):
797                 if not self.targetCos:
798                         return False
799                 elif len(self.targetCos) == 1:
800                         self.setCo(self.targetCos[0])
801                 else:
802                         co_all = Vector()
803                         for co in self.targetCos:
804                                 co_all += co
805                         
806                         self.setCo(co_all / len(self.targetCos))
807                         self.targetCos[:] = []
808                 return True
809         
810         def calcNextMidCo(self):
811                 if not self.next:
812                         return None
813                 
814                 # be tricky later.
815                 self.nextMidCo = (self.co + self.next.co) * 0.5
816         
817         def calcNormal(self):
818                 if self.prev == None:
819                         self.no = (self.next.co - self.co).normalize()
820                 elif self.next == None:
821                         self.no = (self.co - self.prev.co).normalize()
822                 else:
823                         self.no = (self.next.co - self.prev.co).normalize()
824         
825         def calcChildrenMidData(self):
826                 '''
827                 Calculate childrenMidCo & childrenMidRadius
828                 This is a bit tricky, we need to find a point between this and the next,
829                 the medium of all children, this point will be on the line between this and the next.
830                 '''
831                 if not self.next:
832                         return None
833                 
834                 # factor between this and the next point
835                 radius = factor = factor_i = 0.0
836                 
837                 count = 0
838                 for brch in self.children:
839                         if brch: # we dont need the co at teh moment.
840                                 co, fac = ClosestPointOnLine(brch.bpoints[0].co, self.co, self.next.co)
841                                 factor_i += fac
842                                 count += 1
843                                 
844                                 radius += brch.bpoints[0].radius
845                 
846                 if not count:
847                         return
848                 
849                 # interpolate points 
850                 factor_i        = factor_i/count
851                 factor          = 1-factor_i
852                 
853                 self.childrenMidCo = (self.co * factor) + (self.next.co * factor_i)
854                 self.childrenMidRadius = radius
855                 
856                 #debug_pt(self.childrenMidCo)
857                 
858         def getAbsVec(self, index):
859                 # print self.vecs, index
860                 return self.co + self.vecs[index]
861         
862         def slide(self, factor):
863                 '''
864                 Slides the segment up and down using the prev and next points
865                 '''
866                 self.setCo(self.slideCo(factor))
867         
868         def slideCo(self, factor):
869                 if self.prev == None or self.next == None or factor==0.0:
870                         return
871                 
872                 if factor < 0.0:
873                         prev_co = self.prev.co
874                         co = self.co
875                         
876                         ofs = co-prev_co
877                         ofs.length = abs(factor)
878                         self.co - ofs
879                         
880                         return self.co - ofs
881                 else:
882                         next_co = self.next.co
883                         co = self.co
884                         
885                         ofs = co-next_co
886                         ofs.length = abs(factor)
887                         
888                         return self.co - ofs
889                 
890         
891         def collapseDown(self):
892                 '''
893                 Collapse the next point into this one
894                 '''
895                 
896                 # self.next.next == None check is so we dont shorten the final length of branches.
897                 if self.next == None or self.next.next == None or self.isParent or self.next.isParent:
898                         return False
899                 
900                 self.branch.bpoints.remove(self.next)
901                 self.next = self.next.next # skip 
902                 self.next.prev = self
903                 
904                 # Watch this place - must update all data thats needed. roll is not calculaetd yet.
905                 self.calcNextMidCo()
906                 return True
907                 
908         def collapseUp(self):
909                 '''
910                 Collapse the previous point into this one
911                 '''
912                 
913                 # self.next.next == None check is so we dont shorten the final length of branches.
914                 if self.prev == None or self.prev.prev == None or self.prev.isParent or self.prev.prev.isParent:
915                         return False
916                 
917                 self.branch.bpoints.remove(self.prev)
918                 self.prev = self.prev.prev # skip 
919                 self.prev.next = self
920                 
921                 # Watch this place - must update all data thats needed. roll is not calculaetd yet.
922                 self.prev.calcNextMidCo()
923                 return True
924                 
925         
926         def smooth(self, factor, factor_joint):
927                 '''
928                 Blend this point into the other 2 points
929                 '''
930                 if self.next == None or self.prev == None:
931                         return False
932                 
933                 if self.isParent or self.prev.isParent:
934                         factor = factor_joint;
935                 
936                 if factor==0.0:
937                         return False;
938                 
939                 radius = (self.next.radius + self.prev.radius)/2.0
940                 no = (self.next.no + self.prev.no).normalize()
941                 
942                 # do a line intersect to work out the best location
943                 '''
944                 cos = LineIntersect(    self.next.co, self.next.co+self.next.no,\
945                                                                 self.prev.co, self.prev.co+self.prev.no)
946                 if cos == None:
947                         co = (self.prev.co + self.next.co)/2.0
948                 else:
949                         co = (cos[0]+cos[1])/2.0
950                 '''
951                 # Above can give odd results every now and then
952                 co = (self.prev.co + self.next.co)/2.0
953                 
954                 # Now apply
955                 factor_i = 1.0-factor
956                 self.setCo(self.co*factor_i  +  co*factor)
957                 self.radius = self.radius*factor_i  +  radius*factor
958                 
959                 return True
960                 
961         def childPoint(self, index):
962                 '''
963                 Returns the middle point for any children between this and the next edge
964                 '''
965                 if self.next == None:
966                         raise 'Error'
967                 
968                 if index == 0:  return (self.getAbsVec(0) + self.next.getAbsVec(1)) / 2
969                 if index == 1:  return (self.getAbsVec(1) + self.next.getAbsVec(2)) / 2
970                 if index == 2:  return (self.getAbsVec(2) + self.next.getAbsVec(3)) / 2
971                 if index == 3:  return (self.getAbsVec(3) + self.next.getAbsVec(0)) / 2
972         
973         def roll(self, angle):
974                 '''
975                 Roll the quad about its normal 
976                 use for aurienting the sides of a quad to meet a branch that stems from here...
977                 '''
978                 
979                 mat = Blender.Mathutils.RotationMatrix(angle, 3, 'r', self.no)
980                 for i in xrange(4):
981                         self.vecs[i] = self.vecs[i] * mat
982         
983         
984         def toMesh(self, mesh):
985                 self.verts[0].co = self.getAbsVec(0)
986                 self.verts[1].co = self.getAbsVec(1)
987                 self.verts[2].co = self.getAbsVec(2)
988                 self.verts[3].co = self.getAbsVec(3)
989                 
990                 if not self.next:
991                         return
992                 verts = self.verts
993                 next_verts = self.next.verts
994                 
995                 ls = []
996                 if self.prev == None and self.branch.parent_pt:
997                         # join from parent branch
998                         
999                         # which side are we of the parents quad
1000                         index = self.branch.parent_pt.children.index(self.branch)
1001                         
1002                         if index==0:    verts = [self.branch.parent_pt.verts[0], self.branch.parent_pt.verts[1], self.branch.parent_pt.next.verts[1], self.branch.parent_pt.next.verts[0]]
1003                         if index==1:    verts = [self.branch.parent_pt.verts[1], self.branch.parent_pt.verts[2], self.branch.parent_pt.next.verts[2], self.branch.parent_pt.next.verts[1]]
1004                         if index==2:    verts = [self.branch.parent_pt.verts[2], self.branch.parent_pt.verts[3], self.branch.parent_pt.next.verts[3], self.branch.parent_pt.next.verts[2]]
1005                         if index==3:    verts = [self.branch.parent_pt.verts[3], self.branch.parent_pt.verts[0], self.branch.parent_pt.next.verts[0], self.branch.parent_pt.next.verts[3]]
1006                         
1007                         if not self.children[0]:        self.faces[0] = [verts[0], verts[1], next_verts[1], next_verts[0]]
1008                         if not self.children[1]:        self.faces[1] = [verts[1], verts[2], next_verts[2], next_verts[1]]
1009                         if not self.children[2]:        self.faces[2] = [verts[2], verts[3], next_verts[3], next_verts[2]]
1010                         if not self.children[3]:        self.faces[3] = [verts[3], verts[0], next_verts[0], next_verts[3]]
1011                         
1012                 else:
1013                         # normal join
1014                         if not self.children[0]:        self.faces[0] = [verts[0], verts[1], next_verts[1], next_verts[0]]
1015                         if not self.children[1]:        self.faces[1] = [verts[1], verts[2], next_verts[2], next_verts[1]]
1016                         if not self.children[2]:        self.faces[2] = [verts[2], verts[3], next_verts[3], next_verts[2]]
1017                         if not self.children[3]:        self.faces[3] = [verts[3], verts[0], next_verts[0], next_verts[3]]
1018                 
1019                 mesh.faces.extend(ls)
1020         
1021         def calcVerts(self):
1022                 if self.prev == None:
1023                         if self.branch.parent_pt:
1024                                 cross = CrossVecs(self.no, self.branch.getParentFaceCent() - self.branch.parent_pt.getAbsVec( self.branch.getParentQuadIndex() ))
1025                         else:
1026                                 # parentless branch
1027                                 cross = zup
1028                 else:
1029                         cross = CrossVecs(self.prev.vecs[0], self.no)
1030                 
1031                 self.vecs[0] = Blender.Mathutils.CrossVecs(self.no, cross)
1032                 self.vecs[0].length = self.radius
1033                 mat = Blender.Mathutils.RotationMatrix(90, 3, 'r', self.no)
1034                 self.vecs[1] = self.vecs[0] * mat
1035                 self.vecs[2] = self.vecs[1] * mat
1036                 self.vecs[3] = self.vecs[2] * mat
1037         
1038         def hasChildren(self):
1039                 '''
1040                 Use .isParent where possible, this does the real check
1041                 '''
1042                 if self.children.count(None) == 4:
1043                         return False
1044                 else:
1045                         return True
1046         
1047 class branch:
1048         def __init__(self):
1049                 self.bpoints = []
1050                 self.parent_pt = None
1051                 self.tag = False # have we calculated our points
1052                 
1053                 # Bones per branch
1054                 self.bones = []
1055         
1056         def __repr__(self):
1057                 s = ''
1058                 s += '\tbranch'
1059                 s += '\tbpoints:', len(self.bpoints)
1060                 for pt in brch.bpoints:
1061                         s += str(self.pt)
1062                 
1063                 
1064         
1065         def getParentQuadAngle(self):
1066                 '''
1067                 The angle off we are from our parent quad,
1068                 '''
1069                 # used to roll the parent so its faces us better
1070                 parent_normal = self.getParentFaceCent() - self.parent_pt.nextMidCo
1071                 self_normal = self.bpoints[1].co - self.parent_pt.co
1072                 # We only want the angle in relation to the parent points normal
1073                 # modify self_normal to make this so
1074                 cross = CrossVecs(self_normal, self.parent_pt.no)
1075                 self_normal = CrossVecs(self.parent_pt.no, cross) # CHECK
1076                 angle = AngleBetweenVecs(parent_normal, self_normal)
1077                 
1078                 # see if we need to rotate positive or negative
1079                 # USE DOT PRODUCT!
1080                 cross = CrossVecs(parent_normal, self_normal)
1081                 if AngleBetweenVecs(cross, self.parent_pt.no) > 90:
1082                         angle = -angle
1083                 
1084                 return angle
1085         
1086         def getParentQuadIndex(self):
1087                 return self.parent_pt.children.index(self)
1088         def getParentFaceCent(self):
1089                 return self.parent_pt.childPoint(  self.getParentQuadIndex()  )
1090         
1091         def findClosest(self, co):
1092                 ''' # this dosnt work, but could.
1093                 best = None
1094                 best_dist = 100000000
1095                 for pt in self.bpoints:
1096                         if pt.next:
1097                                 co_on_line, fac = ClosestPointOnLine(co, pt.co, pt.next.co)
1098                                 print fac
1099                                 if fac >= 0.0 and fac <= 1.0:
1100                                         return pt, (co-co_on_line).length
1101                 
1102                 return best, best_dist
1103                 '''
1104                 best = None
1105                 best_dist = 100000000
1106                 for pt in self.bpoints:
1107                         if pt.nextMidCo:
1108                                 dist = (pt.nextMidCo-co).length
1109                                 if dist < best_dist:
1110                                         best = pt
1111                                         best_dist = dist
1112                 
1113                 return best, best_dist
1114                 
1115         def evenPointDistrobution(self, factor=1.0, factor_joint=1.0):
1116                 '''
1117                 Redistribute points that are not evenly distributed
1118                 factor is between 0.0 and 1.0
1119                 '''
1120                 
1121                 for pt in self.bpoints:
1122                         if pt.next and pt.prev and pt.isParent == False and pt.prev.isParent == False:
1123                                 w1 = pt.nextLength()
1124                                 w2 = pt.prevLength()
1125                                 wtot = w1+w2
1126                                 w1=w1/wtot
1127                                 #w2=w2/wtot
1128                                 w1 = abs(w1-0.5)*2 # make this from 0.0 to 1.0, where 0 is the middle and 1.0 is as far out of the middle as possible.
1129                                 # print "%.6f" % w1
1130                                 pt.smooth(w1*factor, w1*factor_joint)
1131         
1132         def fixOverlapError(self, joint_smooth=1.0):
1133                 # Keep fixing until no hasOverlapError left to fix.
1134                 error = True
1135                 while error:
1136                         error = False
1137                         for pt in self.bpoints:
1138                                 if pt.prev and pt.next:
1139                                         if pt.hasOverlapError():
1140                                                 if pt.smooth(1.0, joint_smooth): # if we cant fix then dont bother trying again.
1141                                                         error = True
1142         
1143         def evenJointDistrobution(self, joint_compression = 1.0):
1144                 # See if we need to evaluate this branch at all 
1145                 if len(self.bpoints) <= 2: # Rare but in this case we cant do anything
1146                         return
1147                 has_children = False
1148                 for pt in self.bpoints:
1149                         if pt.isParent:
1150                                 has_children = True
1151                                 break
1152                 
1153                 if not has_children:
1154                         return
1155                 
1156                 # OK, we have children, so we have some work to do...
1157                 # center each segment
1158                 
1159                 # work out the median location of all points children.
1160                 for pt in self.bpoints:
1161                         pt.calcChildrenMidData()
1162                 
1163                 for pt in self.bpoints:
1164                         pt.targetCos = []
1165                         if pt.childrenMidCo:
1166                                 # Move this and the next segment to be around the child point.
1167                                 # TODO - factor in the branch angle, be careful with this - close angles can have extreme values.
1168                                 co = pt.slideCo( (pt.childrenMidCo - pt.co).length - (pt.childrenMidRadius * joint_compression) )
1169                                 if co:
1170                                         pt.targetCos.append( co )
1171                                 
1172                                 co = pt.next.slideCo((pt.childrenMidRadius * joint_compression) - (pt.childrenMidCo - pt.next.co).length )
1173                                 if co:
1174                                         pt.next.targetCos.append( co )
1175                 
1176                 for pt in self.bpoints:
1177                         pt.applyTargetLocation()
1178         
1179         def collapsePoints(self, density, smooth_joint=1.0):
1180                 collapse = True
1181                 while collapse:
1182                         collapse = False
1183                         
1184                         pt = self.bpoints[0]
1185                         while pt:
1186                                 
1187                                 if pt.prev and pt.next and not pt.prev.isParent:
1188                                         if (pt.prev.nextMidCo-pt.co).length < ((pt.radius + pt.prev.radius)/2) * density:
1189                                                 pt_save = pt.prev
1190                                                 if pt.next.collapseUp(): # collapse this point
1191                                                         collapse = True
1192                                                         pt = pt_save # so we never reference a removed point
1193                                 
1194                                 if not pt.isParent: #if pt.childrenMidCo == None:
1195                                         # Collapse, if tehre is any problems here we can move into a seperate losop.
1196                                         # do here because we only want to run this on points with no childzren,
1197                                         
1198                                         # Are we closer theto eachother then the radius?
1199                                         if pt.next and (pt.nextMidCo-pt.co).length < ((pt.radius + pt.next.radius)/2) * density:
1200                                                 if pt.collapseDown():
1201                                                         collapse = True
1202                                 
1203                                 pt = pt.next
1204                 ## self.checkPointList()
1205                 self.evenPointDistrobution(1.0, smooth_joint)
1206                 
1207                 for pt in self.bpoints:
1208                         pt.calcNormal()
1209                         pt.calcNextMidCo()
1210         
1211         def branchReJoin(self):
1212                 '''
1213                 Not needed but nice to run after collapsing incase segments moved a lot
1214                 '''
1215                 if not self.parent_pt:
1216                         return # nothing to do
1217                 
1218                 # see if the next segment is closer now (after collapsing)
1219                 par_pt = self.parent_pt
1220                 root_pt = self.bpoints[0]
1221                 
1222                 index = par_pt.children.index(self)
1223                 
1224                 current_dist = (par_pt.nextMidCo - root_pt.co).length
1225                 
1226                 # TODO - Check size of new area is ok to move into
1227                 
1228                 if par_pt.next and par_pt.next.next and par_pt.next.children[index] == None:
1229                         # We can go here if we want, see if its better
1230                         if current_dist > (par_pt.next.nextMidCo - root_pt.co).length:
1231                                 self.parent_pt.children[index] = None
1232                                 self.parent_pt = par_pt.next
1233                                 self.parent_pt.children[index] = self
1234                                 return
1235                 
1236                 if par_pt.prev and par_pt.prev.children[index] == None:
1237                         # We can go here if we want, see if its better
1238                         if current_dist > (par_pt.prev.nextMidCo - root_pt.co).length:
1239                                 self.parent_pt.children[index] = None
1240                                 self.parent_pt.isParent = self.parent_pt.hasChildren()
1241                                 
1242                                 self.parent_pt = par_pt.prev
1243                                 self.parent_pt.children[index] = self
1244                                 self.parent_pt.isParent = True
1245                                 return
1246         
1247         def checkPointList(self):
1248                 '''
1249                 Error checking. use to check if collapsing worked.
1250                 '''
1251                 p_link = self.bpoints[0]
1252                 i = 0
1253                 while p_link:
1254                         if self.bpoints[i] != p_link:
1255                                 raise "Error"
1256                         
1257                         if p_link.prev and p_link.prev != self.bpoints[i-1]:
1258                                 raise "Error Prev"
1259                         
1260                         if p_link.next and p_link.next != self.bpoints[i+1]:
1261                                 raise "Error Next"
1262                         
1263                         p_link = p_link.next
1264                         i+=1
1265
1266         def toMesh(self):
1267                 pass
1268
1269
1270
1271
1272
1273 # No GUI code above this! ------------------------------------------------------
1274
1275 # PREFS - These can be saved on the object's id property. use 'tree2curve' slot
1276 from Blender import Draw
1277 import BPyWindow
1278 ID_SLOT_NAME = 'Curve2Tree'
1279
1280 EVENT_NONE = 0
1281 EVENT_EXIT = 1
1282 EVENT_REDRAW = 2
1283
1284
1285 # Prefs for each tree
1286 PREFS = {}
1287 PREFS['connect_sloppy'] = Draw.Create(1.0)
1288 PREFS['connect_base_trim'] = Draw.Create(1.0)
1289 PREFS['seg_density'] = Draw.Create(0.2)
1290 PREFS['seg_joint_compression'] = Draw.Create(1.0)
1291 PREFS['seg_joint_smooth'] = Draw.Create(2.0)
1292 PREFS['image_main'] = Draw.Create('')
1293 PREFS['do_uv'] = Draw.Create(1)
1294 PREFS['do_subsurf'] = Draw.Create(1)
1295 PREFS['do_uv_scalewidth'] = Draw.Create(1)
1296 PREFS['do_armature'] = Draw.Create(1)
1297 PREFS['do_anim'] = Draw.Create(1)
1298 try:            PREFS['anim_tex'] = Draw.Create([tex for tex in bpy.data.textures][0].name)
1299 except:         PREFS['anim_tex'] = Draw.Create('')
1300
1301 PREFS['anim_speed'] = Draw.Create(0.2)
1302 PREFS['anim_magnitude'] = Draw.Create(0.2)
1303 PREFS['anim_speed_size_scale'] = Draw.Create(1)
1304 PREFS['anim_offset_scale'] = Draw.Create(1.0)
1305
1306 GLOBAL_PREFS = {}
1307
1308 def getContextCurveObjects():
1309         sce = bpy.data.scenes.active
1310         objects = []
1311         for ob in sce.objects.context:
1312                 if ob.type != 'Curve':
1313                         ob = ob.parent
1314                 if ob.type != 'Curve':
1315                         continue
1316                 objects.append(ob)
1317         return objects
1318
1319
1320 def Prefs2Dict(prefs, new_prefs):
1321         '''
1322         Make a copy with no button settings
1323         '''
1324         new_prefs.clear()
1325         for key, val in prefs.items():
1326                 try:    new_prefs[key] = val.val
1327                 except: new_prefs[key] = val
1328         return new_prefs
1329
1330 def Dict2Prefs(prefs, new_prefs):
1331         '''
1332         Make a copy with button settings
1333         '''
1334         for key in prefs: # items would be nice for id groups
1335                 val = prefs[key]
1336                 try:    new_prefs[key] = Blender.Draw.Create( val )
1337                 except: new_prefs[key] = val
1338         return new_prefs
1339
1340 def Prefs2IDProp(idprop, prefs):
1341         new_prefs = {}
1342         Prefs2Dict(prefs, new_prefs)
1343         try:    del idprop[ID_SLOT_NAME]
1344         except: pass
1345         
1346         idprop[ID_SLOT_NAME] = new_prefs
1347         
1348 def IDProp2Prefs(idprop, prefs):
1349         try:    prefs = idprop[ID_SLOT_NAME]
1350         except: return False
1351         Dict2Prefs(prefs, PREFS)
1352         return True
1353         
1354
1355
1356
1357 def buildTree(ob):
1358         '''
1359         Must be a curve object, write to a child mesh
1360         Must check this is a curve object!
1361         '''
1362         
1363         prefs = {}
1364         if not (IDProp2Prefs(ob.properties, prefs)):
1365                 prefs = PREFS
1366         
1367         # Check prefs are ok.
1368         
1369         
1370         sce = bpy.data.scenes.active
1371         
1372         def getCurveChild(obtype):
1373                 try:
1374                         return [ _ob for _ob in sce.objects if _ob.type == obtype if _ob.parent == ob ][0]
1375                 except:
1376                         return None
1377         
1378         def newCurveChild(obdata):
1379                 
1380                 ob_new = bpy.data.scenes.active.objects.new(obdata)
1381                 ob_new.Layers = ob.Layers
1382                 
1383                 # new object settings
1384                 ob.makeParent([ob_new])
1385                 ob_new.setMatrix(Blender.Mathutils.Matrix())
1386                 ob_new.sel = 0
1387                 return ob_new
1388         
1389         def hasModifier(modtype):
1390                 return len([mod for mod in ob_mesh.modifiers if mod.type == modtype]) > 0
1391                         
1392         
1393         sce = bpy.data.scenes.active
1394         
1395         if PREFS['image_main'].val:
1396                 try:            image = bpy.data.images[PREFS['image_main'].val]
1397                 except:         image = None
1398         else:                   image = None
1399         
1400         # Get the mesh child
1401
1402         t = tree()
1403         t.fromCurve(ob)
1404         #print t
1405         t.buildConnections(\
1406                 sloppy = PREFS['connect_sloppy'].val,\
1407                 base_trim = PREFS['connect_base_trim'].val\
1408         )
1409         
1410         t.optimizeSpacing(\
1411                 density=PREFS['seg_density'].val,\
1412                 joint_compression = PREFS['seg_joint_compression'].val,\
1413                 joint_smooth = PREFS['seg_joint_smooth'].val\
1414         )
1415         
1416         ob_mesh = getCurveChild('Mesh')
1417         if not ob_mesh:
1418                 # New object
1419                 mesh = bpy.data.meshes.new('tree_' + ob.name)
1420                 ob_mesh = newCurveChild(mesh)
1421                 # do subsurf later
1422                 
1423         else:
1424                 # Existing object
1425                 mesh = ob_mesh.getData(mesh=1)
1426                 ob_mesh.setMatrix(Blender.Mathutils.Matrix())
1427         
1428         
1429         
1430         mesh = t.toMesh(mesh,\
1431                 do_uvmap = PREFS['do_uv'].val,\
1432                 uv_image = image,\
1433                 do_uv_scalewidth = PREFS['do_uv_scalewidth'].val\
1434         )
1435         
1436         mesh.calcNormals()
1437         
1438         # Do armature stuff....
1439         if PREFS['do_armature'].val:
1440                 ob_arm = getCurveChild('Armature')
1441                 if ob_arm:
1442                         armature = ob_arm.data
1443                         ob_arm.setMatrix(Blender.Mathutils.Matrix())
1444                 else:
1445                         armature = bpy.data.armatures.new()
1446                         ob_arm = newCurveChild(armature)
1447                 
1448                 t.toArmature(ob_arm, armature)
1449                 
1450                 # Add the modifier.
1451                 if not hasModifier(Blender.Modifier.Types.ARMATURE):
1452                         mod = ob_mesh.modifiers.append(Blender.Modifier.Types.ARMATURE)
1453                         
1454                         # TODO - assigne object anyway, even if an existing modifier exists.
1455                         mod[Blender.Modifier.Settings.OBJECT] = ob_arm
1456                 
1457                 if PREFS['do_anim'].val:
1458                         try:
1459                                 tex = bpy.data.textures[PREFS['anim_tex'].val]
1460                         except:
1461                                 tex = None
1462                                 Blender.Draw.PupMenu('error no texture, cannot animate bones')
1463                         
1464                         if tex:
1465                                 t.toAction(ob_arm, tex,\
1466                                                 anim_speed = PREFS['anim_speed'].val,\
1467                                                 anim_magnitude = PREFS['anim_magnitude'].val,\
1468                                                 anim_speed_size_scale= PREFS['anim_speed_size_scale'].val,\
1469                                                 anim_offset_scale=PREFS['anim_offset_scale'].val
1470                                                 )
1471         
1472         # Add subsurf last it needed. so armature skinning is done first.
1473         # Do subsurf?
1474         if PREFS['seg_density'].val:
1475                 if not hasModifier(Blender.Modifier.Types.SUBSURF):
1476                         mod = ob_mesh.modifiers.append(Blender.Modifier.Types.SUBSURF)
1477                         
1478         #ob_mesh.makeDisplayList()
1479         #mesh.update()
1480         bpy.data.scenes.active.update()
1481
1482 def do_pref_read(e,v):
1483         sce = bpy.data.scenes.active
1484         ob = sce.objects.active
1485         
1486         if not ob:
1487                 Blender.Draw.PupMenu('No active curve object')
1488         
1489         if ob.type != 'Curve':
1490                 ob = ob.parent
1491         
1492         if ob.type != 'Curve':
1493                 Blender.Draw.PupMenu('No active curve object')
1494                 return
1495         
1496         if not IDProp2Prefs(ob.properties, PREFS):
1497                 Blender.Draw.PupMenu('Curve object has no settings stored on it')
1498         
1499         Blender.Draw.Redraw()
1500
1501 def do_pref_write(e,v):
1502         
1503         objects = getContextCurveObjects()
1504         if not objects:
1505                 Blender.Draw.PupMenu('No curve objects selected')
1506                 return
1507         
1508         for ob in objects:
1509                 Prefs2IDProp(ob.properties, PREFS)
1510         
1511 def do_pref_clear(e,v):
1512         objects = getContextCurveObjects()
1513         if not objects:
1514                 Blender.Draw.PupMenu('No curve objects selected')
1515                 return
1516         
1517         for ob in objects:
1518                 try:    del idprop[ID_SLOT_NAME]
1519                 except: pass
1520         
1521 #BUTS = {}
1522 #BUTS['']
1523
1524 def do_tex_check(e,v):
1525         try:
1526                 bpy.data.textures[v]
1527         except:
1528                 PREFS['anim_tex'].val = ''
1529                 Draw.PupMenu('Texture dosnt exist!')
1530                 Draw.Redraw()
1531                 
1532
1533 # Button callbacks
1534 def do_active_image(e,v):
1535         img = bpy.data.images.active
1536         if img:
1537                 PREFS['image_main'].val = img.name
1538         else:
1539                 PREFS['image_main'].val = ''
1540
1541 # Button callbacks
1542 def do_tree_generate(e,v):
1543         sce = bpy.data.scenes.active
1544         objects = getContextCurveObjects()
1545         
1546         if not objects:
1547                 Draw.PupMenu('Select one or more curve objects or a mesh/armature types with curve parents')
1548         
1549         is_editmode = Blender.Window.EditMode()
1550         if is_editmode:
1551                 Blender.Window.EditMode(0, '', 0)
1552         
1553         for ob in objects:
1554                 buildTree(ob)
1555         
1556         if is_editmode:
1557                 Blender.Window.EditMode(1, '', 0)
1558         
1559         Blender.Window.RedrawAll()
1560
1561 def evt(e,val):
1562         pass
1563
1564 def bevt(e):
1565         if e == EVENT_REDRAW:
1566                 Draw.Redraw()
1567         if e == EVENT_EXIT:
1568                 Draw.Exit()
1569         pass
1570         
1571 def gui():
1572         MARGIN = 10
1573         rect = BPyWindow.spaceRect()
1574         but_width = 64
1575         but_height = 17
1576         
1577         x=MARGIN
1578         y=rect[3]-but_height-MARGIN
1579         xtmp = x
1580         # ---------- ---------- ---------- ----------
1581         
1582         PREFS['seg_density'] =  Draw.Number('Segment Spacing',EVENT_REDRAW, xtmp, y, but_width*4, but_height, PREFS['seg_density'].val, 0.05, 10.0,     'Scale the limit points collapse, that are closer then the branch width'); xtmp += but_width*4;
1583         
1584         y-=but_height
1585         xtmp = x
1586         # ---------- ---------- ---------- ----------
1587         
1588         PREFS['seg_joint_compression'] =        Draw.Number('Joint Width',      EVENT_NONE, xtmp, y, but_width*4, but_height, PREFS['seg_joint_compression'].val,       0.1, 2.0,       'Edge loop spacing around branch join, lower value for less webed joins'); xtmp += but_width*4;
1589         
1590         y-=but_height
1591         xtmp = x
1592         # ---------- ---------- ---------- ----------
1593         
1594         PREFS['seg_joint_smooth'] =     Draw.Number('Joint Smooth',     EVENT_NONE, xtmp, y, but_width*4, but_height, PREFS['seg_joint_smooth'].val,    0.0, 1.0,       'Edge loop spacing around branch join, lower value for less webed joins'); xtmp += but_width*4;
1595         
1596         y-=but_height
1597         xtmp = x
1598         # ---------- ---------- ---------- ----------
1599         
1600         PREFS['connect_sloppy'] =       Draw.Number('Connect Limit',EVENT_REDRAW, xtmp, y, but_width*4, but_height, PREFS['connect_sloppy'].val,        0.1, 2.0,       'Strictness when connecting branches'); xtmp += but_width*4;
1601         
1602         y-=but_height
1603         xtmp = x
1604         # ---------- ---------- ---------- ----------
1605         
1606         PREFS['connect_base_trim'] =    Draw.Number('Trim Base',        EVENT_NONE, xtmp, y, but_width*4, but_height, PREFS['connect_base_trim'].val,   0.1, 2.0,       'Trim branch base to better connect with parent branch'); xtmp += but_width*4;
1607
1608         y-=but_height
1609         xtmp = x
1610         # ---------- ---------- ---------- ----------
1611         
1612         PREFS['do_subsurf'] =   Draw.Toggle('SubSurf',EVENT_REDRAW, xtmp, y, but_width*4, but_height, PREFS['do_subsurf'].val,          'Enable subsurf for newly generated objects'); xtmp += but_width*4;
1613
1614         y-=but_height+MARGIN
1615         xtmp = x
1616         # ---------- ---------- ---------- ----------
1617
1618         PREFS['do_uv'] =        Draw.Toggle('Generate UVs',EVENT_REDRAW, xtmp, y, but_width*2, but_height, PREFS['do_uv'].val,          'Calculate UVs coords'); xtmp += but_width*2;
1619         if PREFS['do_uv'].val:
1620                 PREFS['do_uv_scalewidth'] =     Draw.Toggle('Keep Aspect',      EVENT_NONE, xtmp, y, but_width*2, but_height, PREFS['do_uv_scalewidth'].val,            'Correct the UV aspect with the branch width'); xtmp += but_width*2;
1621                 
1622                 y-=but_height
1623                 xtmp = x
1624                 # ---------- ---------- ---------- ----------
1625                 
1626                 PREFS['image_main'] =   Draw.String('IM: ',     EVENT_NONE, xtmp, y, but_width*3, but_height, PREFS['image_main'].val, 64,      'Image to apply to faces'); xtmp += but_width*3;
1627                 Draw.PushButton('Use Active',   EVENT_REDRAW, xtmp, y, but_width, but_height,   'Get the image from the active image window', do_active_image); xtmp += but_width;
1628                 
1629         y-=but_height+MARGIN
1630         xtmp = x
1631         # ---------- ---------- ---------- ----------
1632         
1633         PREFS['do_armature'] =  Draw.Toggle('Generate Armature & Skin Mesh',    EVENT_REDRAW, xtmp, y, but_width*4, but_height, PREFS['do_armature'].val,       'Generate Armatuer'); xtmp += but_width*4;
1634         
1635         y-=but_height
1636         xtmp = x
1637         # ---------- ---------- ---------- ----------
1638         if PREFS['do_armature'].val:
1639                 PREFS['do_anim'] =      Draw.Toggle('Texture Anim',     EVENT_REDRAW, xtmp, y, but_width*2, but_height, PREFS['do_anim'].val,   'Use a texture to animate the bones'); xtmp += but_width*2;
1640                 
1641                 if PREFS['do_anim'].val:
1642                         
1643                         PREFS['anim_tex'] =     Draw.String('TEX: ',    EVENT_NONE, xtmp, y, but_width*2, but_height, PREFS['anim_tex'].val, 64,        'Texture to use for the IPO Driver animation', do_tex_check); xtmp += but_width*2;
1644                         y-=but_height
1645                         xtmp = x                
1646                         # ---------- ---------- ---------- ----------
1647                         
1648                         PREFS['anim_speed'] =           Draw.Number('Speed',    EVENT_NONE, xtmp, y, but_width*2, but_height, PREFS['anim_speed'].val,  0.001, 10.0,    'Animate the movement faster with a higher value'); xtmp += but_width*2;
1649                         PREFS['anim_magnitude'] =       Draw.Number('Magnitude',        EVENT_NONE, xtmp, y, but_width*2, but_height, PREFS['anim_magnitude'].val,      0.001, 10.0,    'Animate with more motion with a higher value'); xtmp += but_width*2;
1650                         y-=but_height
1651                         xtmp = x
1652                         # ---------- ---------- ---------- ----------
1653                         
1654                         PREFS['anim_offset_scale'] =    Draw.Number('Unique Offset Scale',      EVENT_NONE, xtmp, y, but_width*4, but_height, PREFS['anim_offset_scale'].val,   0.001, 10.0,    'Use the curve object location as input into the texture so trees have more unique motion, a low value is less unique'); xtmp += but_width*4;
1655                         y-=but_height
1656                         xtmp = x
1657                         
1658                         # ---------- ---------- ---------- ----------
1659                         
1660                         PREFS['anim_speed_size_scale'] =        Draw.Toggle('Branch Size Scales Speed', EVENT_NONE, xtmp, y, but_width*4, but_height, PREFS['anim_speed_size_scale'].val,       'Use the branch size as a factor when calculating speed'); xtmp += but_width*4;
1661         
1662
1663         y-=but_height+MARGIN
1664         xtmp = x
1665         
1666         # ---------- ---------- ---------- ----------
1667         Draw.PushButton('Read Active Prefs',    EVENT_REDRAW, xtmp, y, but_width*2, but_height, 'Read the ID Property settings from the active object', do_pref_read); xtmp += but_width*2;
1668         Draw.PushButton('Write Prefs to Sel',   EVENT_NONE, xtmp, y, but_width*2, but_height,   'Save these settings in the ID Properties of all selected objects', do_pref_write); xtmp += but_width*2;
1669
1670         y-=but_height
1671         xtmp = x
1672         
1673         # ---------- ---------- ---------- ----------
1674         Draw.PushButton('Clear Prefs from Sel', EVENT_NONE, xtmp, y, but_width*4, but_height,   'Remove settings from the selected objects', do_pref_write); xtmp += but_width*4;
1675         do_pref_clear
1676
1677         y-=but_height+MARGIN
1678         xtmp = x
1679         # ---------- ---------- ---------- ----------
1680         
1681         Draw.PushButton('Exit', EVENT_EXIT, xtmp, y, but_width, but_height,     '', do_active_image); xtmp += but_width;
1682         Draw.PushButton('Generate from selection',      EVENT_REDRAW, xtmp, y, but_width*3, but_height, 'Generate mesh', do_tree_generate); xtmp += but_width*3;
1683
1684
1685 if __name__ == '__main__':
1686         Draw.Register(gui, evt, bevt)