addons-contrib: more view_layer syntax updates
[blender-addons-contrib.git] / object_particle_hair_net.py
1 # ---------------------------------------------------
2 # File HairNet.py
3 # Written by Rhett Jackson April 1, 2013
4 # Some routines were copied from "Curve Loop" by Bart Crouch
5 # Some routines were copied from other sources
6 # Very limited at this time:
7 # NB 1) After running the script to create hair,
8 # the user MUST manually enter Particle Mode on the Head object
9 # and "touch" each point of each hair guide.
10 # Using a large comb brish with very low strength is a good way to do this.
11 # If it's not done, the hair strands are likely to:
12 # be reset to a default/straight-out position during editing.
13 # NB 2) All meshes must have the same number of vertices:
14 #  in the direction that corresponds to hair growth
15 # ---------------------------------------------------
16
17 bl_info = {
18         "name": "HairNet",
19         "author": "Rhett Jackson",
20         "version": (0, 5, 1),
21         "blender": (2, 74, 0),
22         "location": "Properties",
23         "category": "Object",
24         "description": "Creates a particle hair system with hair guides from mesh edges which start at marked seams",
25         "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Objects/HairNet",
26         "tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=35062&group_id=153&atid=467"
27         }
28
29 import bpy
30 import mathutils
31 from mathutils import Vector
32 from bpy.utils import register_module, unregister_module
33 from bpy.props import *
34
35 bpy.types.Object.hnMasterHairSystem=StringProperty(
36         name= "hnMasterHairSystem",
37         description= "Name of the hair system to be copied by this proxy object",
38         default="")
39
40 bpy.types.Object.hnIsHairProxy=BoolProperty(
41         name="hnIsHairProxy",
42         description="Is this object a hair proxy object?",
43         default=False)
44
45 bpy.types.Object.hnIsEmitter=BoolProperty(
46         name="hnIsEmitter",
47         description="Is this object a hair emitter object?",
48         default=False)
49
50 bpy.types.Object.hnSproutHairs=IntProperty(
51         name="hnSproutHairs",
52         description="Number of additional hairs to add.",
53         default=0)
54
55 # bpy.types.Object.hnSubdivideHairSections=IntProperty(
56 #         name="hnSubdivideHairSections",
57 #         description="Number of subdivisions to add along the guide hairs",
58 #         default=0)
59
60
61 def debPrintVertEdges(vert_edges):
62     print("vert_edges: ")
63     for vert in vert_edges:
64         print(vert, ": ", vert_edges[vert])
65
66 def debPrintEdgeFaces(edge_faces):
67     print("edge_faces: ")
68     for edge in edge_faces:
69         print(edge, ": ", edge_faces[edge])
70
71 def debPrintEdgeKeys(edges):
72     print("edge_keys")
73     for edge in edges:
74         print(edge, " : ", edge.key)
75
76 def debPrintHairGuides(hairGuides):
77     print("Hair Guides:")
78     guideN=0
79
80     for group in hairGuides:
81         print("Guide #",guideN)
82         i=0
83         for guide in group:
84             print(i, " : ", guide)
85             i += 1
86         guideN+=1
87
88 def debPrintSeams(seamVerts, seamEdges):
89     print("Verts in the seam: ")
90     for vert in seamVerts:
91         print(vert)
92     print("Edges in the seam: ")
93     for edge in seamEdges:
94         print(edge.key)
95
96 def debPrintLoc(func=""):
97     obj = bpy.context.object
98     print(obj.name, " ", func)
99     print("Coords", obj.data.vertices[0].co)
100
101 def getEdgeFromKey(mesh,key):
102     v1 = key[0]
103     v2 = key[1]
104     theEdge = 0
105     for edge in mesh.edges:
106         if v1 in edge.vertices and v2 in edge.vertices:
107             #print("Found edge :", edge.index)
108             return edge
109     return 0
110
111 # returns all edge loops that a vertex is part of
112 def getLoops(obj, v1, vert_edges, edge_faces, seamEdges):
113     debug = False
114
115     me = obj.data
116     if not vert_edges:
117         # Create a dictionary with the vert index as key and edge-keys as value
118         #It's a list of verts and the keys are the edges the verts belong to
119         vert_edges = dict([(v.index, []) for v in me.vertices if v.hide!=1])
120         for ed in me.edges:
121             for v in ed.key:
122                 if ed.key[0] in vert_edges and ed.key[1] in vert_edges:
123                     vert_edges[v].append(ed.key)
124         if debug: debPrintVertEdges(vert_edges)
125     if not edge_faces:
126         # Create a dictionary with the edge-key as key and faces as value
127         # It's a list of edges and the faces they belong to
128         edge_faces = dict([(ed.key, []) for ed in me.edges if (me.vertices[ed.vertices[0]].hide!=1 and me.vertices[ed.vertices[1]].hide!=1)])
129         for f in me.polygons:
130             for key in f.edge_keys:
131                 if key in edge_faces and f.hide!=1:
132                     edge_faces[key].append(f.index)
133         if debug : debPrintEdgeFaces(edge_faces)
134
135     ed_used = [] # starting edges that are already part of a loop that is found
136     edgeloops = [] # to store the final results in
137     for ed in vert_edges[v1.index]: #ed is all the edges v1 is a part of
138         if ed in ed_used:
139             continue
140         seamTest = getEdgeFromKey(me, ed)
141         if seamTest.use_seam:
142             #print("Edge ", seamTest.index, " is a seam")
143             continue
144
145         vloop = [] # contains all verts of the loop
146         poles = [] # contains the poles at the ends of the loop
147         circle = False # tells if loop is circular
148         n = 0 # to differentiate between the start and the end of the loop
149
150         for m in ed: # for each vert in the edge
151             n+=1
152             active_ed = ed
153             active_v  = m
154             if active_v not in vloop:
155                 vloop.insert(0,active_v)
156             else:
157                 break
158             stillGrowing = True
159             while stillGrowing:
160                 stillGrowing = False
161                 active_f = edge_faces[active_ed] #List of faces the edge belongs to
162                 new_ed = vert_edges[active_v] #list of edges the vert belongs to
163                 if len(new_ed)<3: #only 1 or 2 edges
164                     break
165                 if len(new_ed)>4: #5-face intersection
166                     # detect poles and stop growing
167                     if n>1:
168                         poles.insert(0,vloop.pop(0))
169                     else:
170                         poles.append(vloop.pop(-1))
171                     break
172                 for i in new_ed: #new_ed - must have 3 or 4 edges coming from the vert
173                     eliminate = False # if edge shares face, it has to be eliminated
174                     for j in edge_faces[i]: # j is one of the face indices in edge_faces
175                         if j in active_f:
176                             eliminate = True
177                             break
178                     if not eliminate: # it's the next edge in the loop
179                         stillGrowing = True
180                         active_ed = i
181                         if active_ed in vert_edges[v1.index]: #the current edge contains v1
182
183                             ed_used.append(active_ed)
184                         for k in active_ed:
185                             if k != active_v:
186                                 if k not in vloop:
187
188                                     if n>1:
189                                         vloop.insert(0,k)
190                                     else:
191                                         vloop.append(k)
192
193
194                                     active_v = k
195                                     break
196                                 else:
197                                     stillGrowing = False # we've come full circle
198                                     circle = True
199                         break
200         #TODO: Function to sort vloop. Use v1 and edge data to walk the ring in order
201         vloop = sortLoop(obj, vloop, v1, seamEdges, vert_edges)
202         edgeloops.append([vloop, poles, circle])
203     for loop in edgeloops:
204         for vert in loop[0]:
205             me.vertices[vert].select=True
206             #me.edges[edge].select=True
207     return edgeloops, vert_edges, edge_faces
208
209
210
211
212 def getSeams(obj):
213     debug = False
214     #Make a list of all edges marked as seams
215     error = 0
216     seamEdges = []
217     for edge in obj.data.edges:
218         if edge.use_seam:
219             seamEdges.append(edge)
220
221     #Sort the edges in seamEdges
222 #     seamEdges = sortEdges(seamEdges)
223
224     #Make a list of all verts in the seam
225     seamVerts = []
226     for edge in seamEdges:
227         for vert in edge.vertices:
228             if vert not in seamVerts:
229                 seamVerts.append(vert)
230
231     if(len(seamEdges) < 2):
232         error = 2
233         return 0, 0, error
234
235     seamVerts = sortSeamVerts(seamVerts, seamEdges)
236     if debug: debPrintSeams(seamVerts, seamEdges)
237
238     if(len(seamEdges) == 0):
239         error = 2
240
241     return seamVerts, seamEdges, error
242
243 def getNextVertInEdge(edge, vert):
244     if vert == edge.vertices[0]:
245         return edge.vertices[1]
246     else:
247         return edge.vertices[0]
248
249 def makeNewHairSystem(headObject,systemName):
250     bpy.ops.object.mode_set(mode='OBJECT')
251     #Adding a particle modifier also works but requires pushing/pulling the active object and selection.
252     headObject.modifiers.new("HairNet", 'PARTICLE_SYSTEM')
253
254     #Set up context override
255 #    override = {"object": headObject, "particle_system": systemName}
256 #    bpy.ops.object.particle_system_add(override)
257     headObject.particle_systems[-1].name = systemName
258     return headObject.particle_systems[systemName]
259
260 def makePolyLine(objName, curveName, cList):
261     #objName and curveName are strings cList is a list of vectors
262     curveData = bpy.data.curves.new(name=curveName, type='CURVE')
263     curveData.dimensions = '3D'
264
265 #     objectData = bpy.data.objects.new(objName, curveData)
266 #     objectData.location = (0,0,0) #object origin
267 #     bpy.context.scene.objects.link(objectData)
268
269     polyline = curveData.splines.new('BEZIER')
270     polyline.bezier_points.add(len(cList)-1)
271     for num in range(len(cList)):
272         x, y, z = cList[num]
273         polyline.bezier_points[num].co = (x, y, z)
274         polyline.bezier_points[num].handle_left_type = polyline.bezier_points[num].handle_right_type = "AUTO"
275
276 #     return objectData
277     return curveData
278
279 def preserveSelection():
280     #Preserve Active and selected objects
281     storedActive = bpy.context.object
282     storedSelected = []
283     for sel in bpy.context.selected_objects:
284         storedSelected.append(sel)
285
286     return storedActive, storedSelected
287
288
289
290
291 def changeSelection(thisObject):
292     storedActive, storedSelected = preserveSelection()
293
294     bpy.ops.object.select_all(action='DESELECT')
295     bpy.context.view_layer.objects.active=thisObject
296     thisObject.select_set(True)
297
298     return storedActive, storedSelected
299
300 def restoreSelection(storedActive, storedSelected):
301     #Restore active object and selection
302     bpy.context.view_layer.objects.active=storedActive
303     bpy.ops.object.select_all(action='DESELECT')
304     for sel in storedSelected:
305         sel.select_set(True)
306
307 def removeParticleSystem(object, particleSystem):
308     override = {"object": object, "particle_system": particleSystem}
309     bpy.ops.object.particle_system_remove(override)
310
311
312 def sortEdges(edgesList):
313     sorted = []
314     debPrintEdgeKeys(edgesList)
315
316     return edgesList
317
318 def sortLoop(obj, vloop, v1, seamEdges, vert_edges):
319     #The hair is either forward or reversed. If it's reversed, reverse it again. Otherwise do nothing.
320     loop = []
321     loopRange = len(vloop)-1
322
323     if vloop[0] == v1.index:
324         loop = vloop.copy()
325
326     else:
327         loop = vloop[::-1]
328     return loop
329
330 def sortSeamVerts(verts, edges):
331
332     debug = True
333     sortedVerts = []
334     usedEdges = []
335     triedVerts = []
336     triedEdges = []
337     startingVerts = []
338
339     #Make a list of starting points so that each island will have a starting point. Make another "used edges" list
340
341     def findEndpoint(vert):
342         for thisVert in verts:
343             count = 0
344             if thisVert not in triedVerts:
345                 triedVerts.append(thisVert)
346                 #get all edges with thisVert in it
347                 all_edges = [e for e in edges if thisVert in e.vertices]
348
349                 if len(all_edges) == 1:
350                     #The vert is in only one edge and is thus an endpoint
351                     startingVerts.append(thisVert)
352                     #walk to the other end of the seam and add verts to triedVerts
353                     walking = True
354                     thatVert = thisVert
355                     beginEdge = thatEdge = all_edges[0]
356                     while walking:
357                         #get the other vert in the edge
358                         if thatVert == thatEdge.key[0]:
359                             thatVert = thatEdge.key[1]
360                         else:
361                             thatVert = thatEdge.key[0]
362                         #Add current edge to triedEdges
363                         triedEdges.append(thatEdge)
364                         if thatVert not in triedVerts: triedVerts.append(thatVert)
365                         #Put next edge in thatEdge
366                         nextEdge = [e for e in edges if thatVert in e.vertices and e not in triedEdges]
367                         if len(nextEdge) == 1:
368                             #This means one edge was found that wasn't already used
369                             thatEdge = nextEdge[0]
370                         else:
371                             #No unused edges were found
372                             walking = False
373
374     #                 break
375         #at this point, we have found an endpoint
376         if debug:
377             print("seam endpoint", thisVert)
378             print("ending edge", beginEdge.key)
379         #get the edge the vert is in
380         #for thisEdge in edges:
381         return beginEdge, thisVert
382
383     for aVert in verts:
384         if aVert not in triedVerts:
385             thisEdge, thisVert = findEndpoint(aVert)
386
387     #Now, walk through the edges to put the verts in the right order
388
389     for thisVert in startingVerts:
390         thisEdge = [x for x in edges if (thisVert in x.key)][0]
391         sortedVerts.append(thisVert)
392         keepRunning = True
393         while keepRunning:
394             for newVert in thisEdge.key:
395                 if debug: print("next vert is #", newVert)
396                 if thisVert != newVert:
397                     #we have found the other vert if this edge
398                     #store it and find the next edge
399                     thisVert = newVert
400                     sortedVerts.append(thisVert)
401                     usedEdges.append(thisEdge)
402                     break
403             try:
404                 thisEdge = [x for x in edges if ((thisVert in x.key) and (x not in usedEdges))][0]
405             except:
406                 keepRunning = False
407             if debug: print("next vert is in edge", thisEdge.key)
408
409
410
411
412     return sortedVerts
413
414
415
416 def totalNumberSubdivisions(points, cuts):
417     return points + (points - 1)*cuts
418
419 class HairNet (bpy.types.Operator):
420     bl_idname = "particle.hairnet"
421     bl_label = "HairNet"
422     bl_options = {'REGISTER', 'UNDO'}
423     bl_description = "Makes hair guides from mesh edges."
424
425     meshKind: StringProperty()
426
427     targetHead = False
428     headObj = 0
429     hairObjList = []
430     hairProxyList = []
431
432     @classmethod
433
434     def poll(self, context):
435         return(context.mode == 'OBJECT')
436
437     def execute(self, context):
438         error = 0   #0 = All good
439                     #1 = Hair guides have different lengths
440                     #2 = No seams in hair object
441                     #3 = Bevel on curve object
442
443         targetObject = self.headObj
444
445         for thisHairObj in self.hairObjList:
446             options = [
447                        0,                   #0 the hair system's previous settings
448                        thisHairObj,         #1 The hair object
449                        0,                   #2 The hair system. So we don't have to rely on the selected system
450                        self.targetHead      #3 Target a head object?
451                        ]
452             #A new hair object gets a new guides list
453             hairGuides = []
454
455             #if not self.targetHead:
456             if thisHairObj.hnIsEmitter:
457                 targetObject = thisHairObj
458
459             sysName = ''.join(["HN", thisHairObj.name])
460
461             if sysName in targetObject.particle_systems:
462                 #if this proxy object has an existing hair system on the target object, preserve its current settings
463                 if thisHairObj.hnMasterHairSystem == "":
464                     '''_TS Preserve and out'''
465                     options[0] = targetObject.particle_systems[sysName].settings
466                     options[2] = targetObject.particle_systems[sysName]
467
468                 else:
469                     '''TS Delete settings, copy, and out'''
470                     #Store a link to the system settings so we can delete the settings
471                     delSet = targetObject.particle_systems[sysName].settings
472                     #Get active_index of desired particle system
473                     bpy.context.object.particle_systems.active_index = bpy.context.object.particle_systems.find(sysName)
474                     #Delete Particle System
475                     removeParticleSystem(targetObject, targetObject.particle_systems[sysName])
476                     #Delete Particle System Settings
477                     bpy.data.particles.remove(delSet)
478                     #Copy Hair settings from master.
479                     options[0] = bpy.data.particles[thisHairObj.hnMasterHairSystem].copy()
480
481                     options[2] = makeNewHairSystem(targetObject,sysName)
482             else:
483                 #Create a new hair system
484                 if thisHairObj.hnMasterHairSystem != "":
485                     '''T_S copy, create new and out'''
486                     options[0] = bpy.data.particles[thisHairObj.hnMasterHairSystem].copy()
487 #                     options[2] = self.headObj.particle_systems[sysName]
488
489                 '''_T_S create new and out'''
490                 options[2] = makeNewHairSystem(targetObject,sysName)
491
492             if (self.meshKind=="SHEET"):
493                 print("Hair sheet "+ thisHairObj.name)
494                 #Create all hair guides
495                 #for hairObj in self.hairObjList:
496                 #Identify the seams and their vertices
497                 #Start looking here for multiple mesh problems.
498                 seamVerts, seamEdges, error = getSeams(thisHairObj)
499
500                 if(error == 0):
501                     vert_edges = edge_faces = False
502                     #For every vert in a seam, get the edge loop spawned by it
503                     for thisVert in seamVerts:
504                         edgeLoops, vert_edges, edge_faces = getLoops(thisHairObj, thisHairObj.data.vertices[thisVert], vert_edges, edge_faces, seamEdges)
505                         '''Is loopsToGuides() adding to the count of guides instead of overwriting?'''
506                         hairGuides = self.loopsToGuides(thisHairObj, edgeLoops, hairGuides)
507                     debPrintHairGuides(hairGuides)
508                     #Take each edge loop and extract coordinate data from its verts
509
510             if (self.meshKind=="FIBER"):
511                 hairObj = self.hairObjList[0]
512                 print("Hair fiber")
513                 hairGuides = self.fibersToGuides(hairObj)
514
515             if (self.meshKind=="CURVE"):
516                 #Preserve Active and selected objects
517                 tempActive = headObj = bpy.context.object
518                 tempSelected = []
519                 tempSelected.append(bpy.context.selected_objects[0])
520                 tempSelected.append(bpy.context.selected_objects[1])
521                 #hairObj = bpy.context.selected_objects[0]
522                 hairObj = thisHairObj
523                 bpy.ops.object.select_all(action='DESELECT')
524
525                 if hairObj.data.bevel_object != None:
526                     error = 3
527
528
529                 bpy.context.scene.objects.active=hairObj
530                 hairObj.select=True
531
532                 print("Curve Head: ", headObj.name)
533                 bpy.ops.object.convert(target='MESH', keep_original=True)
534                 fiberObj = bpy.context.active_object
535
536                 print("Hair Fibers: ", fiberObj.name)
537                 print("Hair Curves: ", hairObj.name)
538
539                 hairGuides = self.fibersToGuides(fiberObj)
540
541                 bpy.ops.object.delete(use_global=False)
542
543                 #Restore active object and selection
544                 bpy.context.scene.objects.active=tempActive
545                 bpy.ops.object.select_all(action='DESELECT')
546                 for sel in tempSelected:
547                     sel.select = True
548     #            return {'FINISHED'}
549
550             if (self.checkGuides(hairGuides)):
551                 error = 1
552
553             #Process errors
554             if error != 0:
555                 if error == 1:
556                     self.report(type = {'ERROR'}, message = "Mesh guides have different lengths")
557                 if error == 2:
558                     self.report(type = {'ERROR'}, message = ("No seams were defined in " + targetObject.name))
559                     removeParticleSystem(targetObject, options[2])
560                 if error == 3:
561                     self.report(type = {'ERROR'}, message = "Cannot create hair from curves with a bevel object")
562                 return{'CANCELLED'}
563
564             #Subdivide hairs
565             hairGuides = self.subdivideGuideHairs(hairGuides, thisHairObj)
566
567             #Create the hair guides on the hair object
568             self.createHair(targetObject, hairGuides, options)
569
570         return {'FINISHED'}
571
572     def invoke (self, context, event):
573
574         self.headObj = bpy.context.object
575
576         #Get a list of hair objects
577         self.hairObjList = []
578         for obj in bpy.context.selected_objects:
579             if obj != self.headObj or obj.hnIsEmitter:
580                 self.hairObjList.append(obj)
581
582
583         #if the last object selected is not flagged as a self-emitter, then assume we are creating hair on a head
584         #Otherwise, each proxy will grow its own hair
585
586         if not self.headObj.hnIsEmitter:
587             self.targetHead=True
588             if len(bpy.context.selected_objects) < 2:
589                 self.report(type = {'ERROR'}, message = "Selection too small. Please select two objects")
590                 return {'CANCELLED'}
591         else:
592             self.targetHead=False
593
594
595
596
597         return self.execute(context)
598
599     def checkGuides(self, hairGuides):
600         length = 0
601         for guide in hairGuides:
602             if length == 0:
603                 length = len(guide)
604             else:
605                 if length != len(guide):
606                     return 1
607         return 0
608
609     def createHair(self, ob, guides, options):
610         debug = False
611
612         tempActive = bpy.context.scene.objects.active
613         bpy.context.scene.objects.active = ob
614
615         if debug: print("Active Object: ", bpy.context.scene.objects.active.name)
616
617         nGuides = len(guides)
618         if debug: print("nGguides", nGuides)
619         nSteps = len(guides[0])
620         if debug: print("nSteps", nSteps)
621
622         # Create hair particle system if  needed
623         #bpy.ops.object.mode_set(mode='OBJECT')
624         #bpy.ops.object.particle_system_add()
625
626         psys = options[2]
627
628         # Particle settings
629         pset = psys.settings
630
631         if options[0] != 0:
632             #Use existing settings
633             psys.settings = options[0]
634             pset = options[0]
635         else:
636             #Create new settings
637             pset.type = 'HAIR'
638
639             pset.emit_from = 'FACE'
640             pset.use_render_emitter = False
641             pset.use_strand_primitive = True
642
643             # Children
644             pset.child_type = 'SIMPLE'
645             pset.child_nbr = 6
646             pset.rendered_child_count = 50
647             pset.child_length = 1.0
648             pset.child_length_threshold = 0.0
649             pset.child_radius = 0.1
650             pset.child_roundness = 1.0
651
652         #Rename Hair Settings
653         pset.name = ''.join([options[2].name, " Hair Settings"])
654         pset.hair_step = nSteps-1
655         #This set the number of guides for the particle system. It may have to be the same for every instance of the system.
656         pset.count = nGuides
657
658         #Render the emitter object?
659         if options[3]:
660             pset.use_render_emitter = True
661         else:
662             pset.use_render_emitter = False
663
664         # Disconnect hair and switch to particle edit mode
665
666
667         # Set all hair-keys
668 #         dt = 100.0/(nSteps-1)
669 #         dw = 1.0/(nSteps-1)
670
671         # Connect hair to mesh
672         # Segmentation violation during render if this line is absent.
673         # Connecting hair moves the mesh points by an amount equal to the object's location
674
675         bpy.ops.particle.particle_edit_toggle()
676         bpy.context.scene.tool_settings.particle_edit.use_emitter_deflect = False
677         bpy.context.scene.tool_settings.particle_edit.use_preserve_root = False
678         bpy.context.scene.tool_settings.particle_edit.use_preserve_length = False
679
680         bpy.ops.particle.disconnect_hair(all=True)
681         #Connecting and disconnecting hair causes them to jump when other particle systems are created.
682         bpy.ops.particle.connect_hair(all=True)
683
684         for m in range(nGuides):
685             #print("Working on guide #", m)
686             nSteps = len(guides[m])
687             guide = guides[m]
688             part = psys.particles[m]
689             part.location = guide[0]
690
691             #print("Guide #", m)
692             for n in range(0, nSteps):
693                 point = guide[n]
694                 #print("Hair point #", n, ": ", point)
695                 h = part.hair_keys[n]
696                 #h.co_local = point
697                 h.co = point
698     #            print("#", n, " = ", point)
699     #            h.time = n*dt
700     #            h.weight = 1.0 - n*dw
701
702         # Toggle particle edit mode
703         bpy.ops.particle.particle_edit_toggle()
704
705
706         bpy.context.scene.objects.active = tempActive
707         return
708
709     def createHairGuides(self, obj, edgeLoops):
710         hairGuides = []
711
712         #For each loop
713         for loop in edgeLoops:
714             thisGuide = []
715             #For each vert in the loop
716             for vert in loop[0]:
717                 thisGuide.append(obj.data.vertices[vert].co)
718             hairGuides.append(thisGuide)
719
720         return hairGuides
721
722     def fibersToGuides(self, hairObj):
723         guides = []
724         hairs = self.getHairsFromFibers(hairObj)
725
726         for hair in hairs:
727             guide = []
728             for vertIdx in hair:
729                 guide.append(hairObj.data.vertices[vertIdx].co.to_tuple())
730             guides.append(guide)
731         return guides
732
733     def getHairsFromFibers(self, hair):
734         me = hair.data
735         usedV = []
736         usedE = []
737         guides = []
738
739         # Create a dictionary with the vert index as key and edge-keys as value
740         #It's a list of verts and the keys are the edges the verts belong to
741         vert_edges = dict([(v.index, []) for v in me.vertices if v.hide!=1])
742         for ed in me.edges:
743             for v in ed.key:
744                 if ed.key[0] in vert_edges and ed.key[1] in vert_edges:
745                     vert_edges[v].append(ed.key)
746
747         #endPoints = dict([(v, []) for v in vert_edges if len(vert_edges[v])<2])
748         endPoints = [v for v in vert_edges if len(vert_edges[v])<2]
749
750         #For every endpoint
751         for vert in endPoints:
752                 hair=[]
753                 #print("first endpoint is ", vert)
754                 #check if EP has been used already in case it was a tail end already
755                 if vert not in usedV:
756                     #lookup the endpoint in vert_edges to get the edge(s) it's in
757                     thisEdge = getEdgeFromKey(me,vert_edges[vert][0])
758                     #print("Got edge ", thisEdge)
759                     #Add the vert to the hair
760                     hair.append(vert)
761                     #mark the current vert as used
762                     #mark the current edge as used
763                     usedE.append(thisEdge)
764                     usedV.append(vert)
765                     #get the next/other vert in the edge
766                     #make it the current vert
767                     vert = getNextVertInEdge(thisEdge,vert)
768                     #print("got next vert ", vert, " edges", vert_edges[vert])
769                     #while the number of edges the current vert is  > 1
770                     while len(vert_edges[vert])>1:
771                         #lookup the endpoint in vert_edges to get the edge(s) it's in
772                         thisEdge = getEdgeFromKey(me,vert_edges[vert][0])
773
774                         if thisEdge in usedE:
775                             thisEdge = getEdgeFromKey(me,vert_edges[vert][1])
776                         #Add the vert to the hair
777                         hair.append(vert)
778                         #mark the current vert as used
779                         #mark the current edge as used
780                         usedE.append(thisEdge)
781                         usedV.append(vert)
782                         #get the next/other vert in the edge
783                         #make it the current vert
784                         vert = getNextVertInEdge(thisEdge,vert)
785                         #print("vert #", vert)
786                         #print("edge #", thisEdge)
787                         #print(vert_edges[vert])
788
789
790                     #Add the current vert to the hair
791                     hair.append(vert)
792                     #mark the current vert as used
793                     usedV.append(vert)
794                     #add the hair to the list of hairs
795                     guides.append(hair)
796
797         #guides now holds a list of hairs where each hair is a list of vertex indices in the mesh "me"
798         return guides
799
800     def loopsToGuides(self, obj, edgeLoops, hairGuides):
801         guides = hairGuides
802         #guides = []
803
804         for loop in edgeLoops:
805             hair = []
806             #hair is a list of coordinate sets. guides is a list of lists
807             for vert in loop[0]:
808                 #co originally came through as a tuple. Is a Vector better?
809                 hair.append(obj.data.vertices[vert].co)
810     #             hair.append(obj.data.vertices[vert].co.to_tuple())
811             guides.append(hair)
812         return guides
813
814     def subdivideGuideHairs(self, guides, hairObj):
815         debug = True
816         #number of points in original guide hair
817         hairLength = len(guides[0])
818
819         #original number of hairs
820         numberHairs = len(guides)
821
822         #number of hairs added between existing hairs
823         hairSprouts = hairObj.hnSproutHairs
824
825         #subdivide hairs
826         if hairObj.hnSproutHairs > 0:
827             #initialize an empty array so we don't have to think about inserting entries into lists. Check into this for later?
828             newHairs = [[0 for i in range(hairLength)] for j in range(totalNumberSubdivisions(numberHairs, hairSprouts))]
829             if debug: print ("Subdivide Hairs")
830             newNumber = 1
831
832             #initial condition
833             start = guides[0][0]
834             newHairs[0][0] = start
835     #         debPrintHairGuides(newHairs)
836             #for every hair pair, start at the root and send groups of four guide points to the interpolator
837             #index identifies which row is current
838             #kndex identifies the current hair in the list of new points
839             #jndex identifies the current hair in the old list of hairs
840             for index in range(0, hairLength):
841                 if debug: print("Hair Row ", index)
842                 #add the first hair's points
843                 newHairs[0][index] = guides[0][index]
844                 #Make a curve from the points in this row
845                 thisRow = []
846                 for aHair in guides:
847                     thisRow.append(aHair[index])
848                 curveObject = makePolyLine("rowCurveObj", "rowCurve", thisRow)
849                 for jndex in range(0, numberHairs-1):
850     #                 knot1 = curveObject.data.splines[0].bezier_points[jndex]
851     #                 knot2 = curveObject.data.splines[0].bezier_points[jndex + 1]
852                     knot1 = curveObject.splines[0].bezier_points[jndex]
853                     knot2 = curveObject.splines[0].bezier_points[jndex + 1]
854                     handle1 = knot1.handle_right
855                     handle2 = knot2.handle_left
856                     newPoints = mathutils.geometry.interpolate_bezier(knot1.co, handle1, handle2, knot2.co, hairSprouts+2)
857
858
859                     #add new points to the matrix
860                     #interpolate_bezier includes the endpoints so, for now, skip over them. re-write later to be a cleaner algorithm
861                     for kndex in range(0, len(newPoints)-2):
862                         newHairs[1+kndex+jndex*(1+hairSprouts)][index] = newPoints[kndex+1]
863     #                     if debug: print("newHairs[", 1+kndex+jndex*(1+hairSprouts), "][", index, "] = ", newPoints[kndex], "SubD")
864     #                     newHairs[jndex*(1+hairSprouts)][index] = newPoints[kndex]
865     #                     print("knot1 = ", knot1)
866     #                     print("knot2 = ", knot2)
867     #                     print("newHairs[", 1+kndex+jndex*(1+hairSprouts), "][", index, "] = ", newPoints[kndex])
868                         newNumber = newNumber + 1
869
870
871                     #add the end point
872                     newHairs[(jndex+1)*(hairSprouts+1)][index] = guides[jndex+1][index]
873     #                 if debug: print("newHairs[", (jndex+1)*(hairSprouts+1), "][", index, "] = ", guides[jndex][index], "Copy")
874                     newNumber = newNumber + 1
875
876                 #clean up the curve we created
877                 bpy.data.curves.remove(curveObject)
878             if debug:
879                 print("NewHairs")
880                 debPrintHairGuides(newHairs)
881             guides = newHairs
882
883         return guides
884
885 class HairNetPanel(bpy.types.Panel):
886     bl_idname = "PARTICLE_PT_HairNet"
887     bl_space_type = "PROPERTIES"
888     bl_region_type = "WINDOW"
889     bl_context = "particle"
890     bl_label = "HairNet 0.4.11"
891
892
893
894     def draw(self, context):
895
896         self.headObj = context.object
897
898         #Get a list of hair objects
899         self.hairObjList = context.selected_objects
900         self.hairObjList.remove(self.headObj)
901
902         layout = self.layout
903
904         row = layout.row()
905         row.label("Objects Start here")
906
907         '''Is this a hair object?'''
908
909         row = layout.row()
910         try:
911 #             row.prop(self.headObj, 'hnIsHairProxy', text = "Hair Proxy")
912             row.prop(self.headObj, 'hnIsEmitter', text = "Emit Hair on Self")
913         except:
914             pass
915
916         #Draw this if this is a head object
917         if not self.headObj.hnIsEmitter:
918             box = layout.box()
919             row = box.row()
920             row.label(text="Hair Object:")
921             row.label(text="Master Hair System:")
922             for thisHairObject in self.hairObjList:
923                 row = box.row()
924                 row.prop_search(thisHairObject, 'hnMasterHairSystem',  bpy.data, "particles", text = thisHairObject.name)
925                 row = box.row()
926                 row.label(text="Guide Subdivisions:")
927                 row.prop(thisHairObject, 'hnSproutHairs', text = "Subdivide U")
928 #                 row.prop(thisHairObject, 'hnSubdivideHairSections', text = "Subdivide V")
929
930
931
932
933         #Draw this if it's a self-emitter object
934         else:
935             box = layout.box()
936             try:
937
938
939                 row = box.row()
940                 row.label(text="Master Hair System")
941                 row = box.row()
942                 row.prop_search(self.headObj, 'hnMasterHairSystem',  bpy.data, "particles", text = self.headObj.name)
943
944             except:
945                 pass
946             row = box.row()
947             row.label(text="Guide Subdivisions:")
948             row.prop(self.headObj, 'hnSproutHairs', text = "Subdivide U")
949
950         row = layout.row()
951         row.operator("particle.hairnet", text="Add Hair From Sheets").meshKind="SHEET"
952         row = layout.row()
953         row.operator("particle.hairnet", text="Add Hair From Fibers").meshKind="FIBER"
954         row = layout.row()
955         row.operator("particle.hairnet", text="Add Hair From Curves").meshKind="CURVE"
956
957
958
959
960 def register():
961     unregister_module(__name__)
962     register_module(__name__)
963
964
965
966
967 def unregister():
968     unregister_module(__name__)
969
970 if __name__ == '__main__':
971     register()