# --------------------------------------------------- # File HairNet.py # Written by Rhett Jackson April 1, 2013 # Some routines were copied from "Curve Loop" by Bart Crouch # Some routines were copied from other sources # Very limited at this time: # NB 1) After running the script to create hair, # the user MUST manually enter Particle Mode on the Head object # and "touch" each point of each hair guide. # Using a large comb brish with very low strength is a good way to do this. # If it's not done, the hair strands are likely to: # be reset to a default/straight-out position during editing. # NB 2) All meshes must have the same number of vertices: # in the direction that corresponds to hair growth # --------------------------------------------------- bl_info = { "name": "HairNet", "author": "Rhett Jackson", "version": (0, 5, 1), "blender": (2, 74, 0), "location": "Properties", "category": "Object", "description": "Creates a particle hair system with hair guides from mesh edges which start at marked seams", "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Objects/HairNet", "tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=35062&group_id=153&atid=467" } import bpy import mathutils from mathutils import Vector from bpy.utils import register_module, unregister_module from bpy.props import * bpy.types.Object.hnMasterHairSystem=StringProperty( name= "hnMasterHairSystem", description= "Name of the hair system to be copied by this proxy object", default="") bpy.types.Object.hnIsHairProxy=BoolProperty( name="hnIsHairProxy", description="Is this object a hair proxy object?", default=False) bpy.types.Object.hnIsEmitter=BoolProperty( name="hnIsEmitter", description="Is this object a hair emitter object?", default=False) bpy.types.Object.hnSproutHairs=IntProperty( name="hnSproutHairs", description="Number of additional hairs to add.", default=0) # bpy.types.Object.hnSubdivideHairSections=IntProperty( # name="hnSubdivideHairSections", # description="Number of subdivisions to add along the guide hairs", # default=0) def debPrintVertEdges(vert_edges): print("vert_edges: ") for vert in vert_edges: print(vert, ": ", vert_edges[vert]) def debPrintEdgeFaces(edge_faces): print("edge_faces: ") for edge in edge_faces: print(edge, ": ", edge_faces[edge]) def debPrintEdgeKeys(edges): print("edge_keys") for edge in edges: print(edge, " : ", edge.key) def debPrintHairGuides(hairGuides): print("Hair Guides:") guideN=0 for group in hairGuides: print("Guide #",guideN) i=0 for guide in group: print(i, " : ", guide) i += 1 guideN+=1 def debPrintSeams(seamVerts, seamEdges): print("Verts in the seam: ") for vert in seamVerts: print(vert) print("Edges in the seam: ") for edge in seamEdges: print(edge.key) def debPrintLoc(func=""): obj = bpy.context.object print(obj.name, " ", func) print("Coords", obj.data.vertices[0].co) def getEdgeFromKey(mesh,key): v1 = key[0] v2 = key[1] theEdge = 0 for edge in mesh.edges: if v1 in edge.vertices and v2 in edge.vertices: #print("Found edge :", edge.index) return edge return 0 # returns all edge loops that a vertex is part of def getLoops(obj, v1, vert_edges, edge_faces, seamEdges): debug = False me = obj.data if not vert_edges: # Create a dictionary with the vert index as key and edge-keys as value #It's a list of verts and the keys are the edges the verts belong to vert_edges = dict([(v.index, []) for v in me.vertices if v.hide!=1]) for ed in me.edges: for v in ed.key: if ed.key[0] in vert_edges and ed.key[1] in vert_edges: vert_edges[v].append(ed.key) if debug: debPrintVertEdges(vert_edges) if not edge_faces: # Create a dictionary with the edge-key as key and faces as value # It's a list of edges and the faces they belong to 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)]) for f in me.polygons: for key in f.edge_keys: if key in edge_faces and f.hide!=1: edge_faces[key].append(f.index) if debug : debPrintEdgeFaces(edge_faces) ed_used = [] # starting edges that are already part of a loop that is found edgeloops = [] # to store the final results in for ed in vert_edges[v1.index]: #ed is all the edges v1 is a part of if ed in ed_used: continue seamTest = getEdgeFromKey(me, ed) if seamTest.use_seam: #print("Edge ", seamTest.index, " is a seam") continue vloop = [] # contains all verts of the loop poles = [] # contains the poles at the ends of the loop circle = False # tells if loop is circular n = 0 # to differentiate between the start and the end of the loop for m in ed: # for each vert in the edge n+=1 active_ed = ed active_v = m if active_v not in vloop: vloop.insert(0,active_v) else: break stillGrowing = True while stillGrowing: stillGrowing = False active_f = edge_faces[active_ed] #List of faces the edge belongs to new_ed = vert_edges[active_v] #list of edges the vert belongs to if len(new_ed)<3: #only 1 or 2 edges break if len(new_ed)>4: #5-face intersection # detect poles and stop growing if n>1: poles.insert(0,vloop.pop(0)) else: poles.append(vloop.pop(-1)) break for i in new_ed: #new_ed - must have 3 or 4 edges coming from the vert eliminate = False # if edge shares face, it has to be eliminated for j in edge_faces[i]: # j is one of the face indices in edge_faces if j in active_f: eliminate = True break if not eliminate: # it's the next edge in the loop stillGrowing = True active_ed = i if active_ed in vert_edges[v1.index]: #the current edge contains v1 ed_used.append(active_ed) for k in active_ed: if k != active_v: if k not in vloop: if n>1: vloop.insert(0,k) else: vloop.append(k) active_v = k break else: stillGrowing = False # we've come full circle circle = True break #TODO: Function to sort vloop. Use v1 and edge data to walk the ring in order vloop = sortLoop(obj, vloop, v1, seamEdges, vert_edges) edgeloops.append([vloop, poles, circle]) for loop in edgeloops: for vert in loop[0]: me.vertices[vert].select=True #me.edges[edge].select=True return edgeloops, vert_edges, edge_faces def getSeams(obj): debug = False #Make a list of all edges marked as seams error = 0 seamEdges = [] for edge in obj.data.edges: if edge.use_seam: seamEdges.append(edge) #Sort the edges in seamEdges # seamEdges = sortEdges(seamEdges) #Make a list of all verts in the seam seamVerts = [] for edge in seamEdges: for vert in edge.vertices: if vert not in seamVerts: seamVerts.append(vert) if(len(seamEdges) < 2): error = 2 return 0, 0, error seamVerts = sortSeamVerts(seamVerts, seamEdges) if debug: debPrintSeams(seamVerts, seamEdges) if(len(seamEdges) == 0): error = 2 return seamVerts, seamEdges, error def getNextVertInEdge(edge, vert): if vert == edge.vertices[0]: return edge.vertices[1] else: return edge.vertices[0] def makeNewHairSystem(headObject,systemName): bpy.ops.object.mode_set(mode='OBJECT') #Adding a particle modifier also works but requires pushing/pulling the active object and selection. headObject.modifiers.new("HairNet", 'PARTICLE_SYSTEM') #Set up context override # override = {"object": headObject, "particle_system": systemName} # bpy.ops.object.particle_system_add(override) headObject.particle_systems[-1].name = systemName return headObject.particle_systems[systemName] def makePolyLine(objName, curveName, cList): #objName and curveName are strings cList is a list of vectors curveData = bpy.data.curves.new(name=curveName, type='CURVE') curveData.dimensions = '3D' # objectData = bpy.data.objects.new(objName, curveData) # objectData.location = (0,0,0) #object origin # bpy.context.scene.objects.link(objectData) polyline = curveData.splines.new('BEZIER') polyline.bezier_points.add(len(cList)-1) for num in range(len(cList)): x, y, z = cList[num] polyline.bezier_points[num].co = (x, y, z) polyline.bezier_points[num].handle_left_type = polyline.bezier_points[num].handle_right_type = "AUTO" # return objectData return curveData def preserveSelection(): #Preserve Active and selected objects storedActive = bpy.context.object storedSelected = [] for sel in bpy.context.selected_objects: storedSelected.append(sel) return storedActive, storedSelected def changeSelection(thisObject): storedActive, storedSelected = preserveSelection() bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.objects.active=thisObject thisObject.select_set(True) return storedActive, storedSelected def restoreSelection(storedActive, storedSelected): #Restore active object and selection bpy.context.view_layer.objects.active=storedActive bpy.ops.object.select_all(action='DESELECT') for sel in storedSelected: sel.select_set(True) def removeParticleSystem(object, particleSystem): override = {"object": object, "particle_system": particleSystem} bpy.ops.object.particle_system_remove(override) def sortEdges(edgesList): sorted = [] debPrintEdgeKeys(edgesList) return edgesList def sortLoop(obj, vloop, v1, seamEdges, vert_edges): #The hair is either forward or reversed. If it's reversed, reverse it again. Otherwise do nothing. loop = [] loopRange = len(vloop)-1 if vloop[0] == v1.index: loop = vloop.copy() else: loop = vloop[::-1] return loop def sortSeamVerts(verts, edges): debug = True sortedVerts = [] usedEdges = [] triedVerts = [] triedEdges = [] startingVerts = [] #Make a list of starting points so that each island will have a starting point. Make another "used edges" list def findEndpoint(vert): for thisVert in verts: count = 0 if thisVert not in triedVerts: triedVerts.append(thisVert) #get all edges with thisVert in it all_edges = [e for e in edges if thisVert in e.vertices] if len(all_edges) == 1: #The vert is in only one edge and is thus an endpoint startingVerts.append(thisVert) #walk to the other end of the seam and add verts to triedVerts walking = True thatVert = thisVert beginEdge = thatEdge = all_edges[0] while walking: #get the other vert in the edge if thatVert == thatEdge.key[0]: thatVert = thatEdge.key[1] else: thatVert = thatEdge.key[0] #Add current edge to triedEdges triedEdges.append(thatEdge) if thatVert not in triedVerts: triedVerts.append(thatVert) #Put next edge in thatEdge nextEdge = [e for e in edges if thatVert in e.vertices and e not in triedEdges] if len(nextEdge) == 1: #This means one edge was found that wasn't already used thatEdge = nextEdge[0] else: #No unused edges were found walking = False # break #at this point, we have found an endpoint if debug: print("seam endpoint", thisVert) print("ending edge", beginEdge.key) #get the edge the vert is in #for thisEdge in edges: return beginEdge, thisVert for aVert in verts: if aVert not in triedVerts: thisEdge, thisVert = findEndpoint(aVert) #Now, walk through the edges to put the verts in the right order for thisVert in startingVerts: thisEdge = [x for x in edges if (thisVert in x.key)][0] sortedVerts.append(thisVert) keepRunning = True while keepRunning: for newVert in thisEdge.key: if debug: print("next vert is #", newVert) if thisVert != newVert: #we have found the other vert if this edge #store it and find the next edge thisVert = newVert sortedVerts.append(thisVert) usedEdges.append(thisEdge) break try: thisEdge = [x for x in edges if ((thisVert in x.key) and (x not in usedEdges))][0] except: keepRunning = False if debug: print("next vert is in edge", thisEdge.key) return sortedVerts def totalNumberSubdivisions(points, cuts): return points + (points - 1)*cuts class HairNet (bpy.types.Operator): bl_idname = "particle.hairnet" bl_label = "HairNet" bl_options = {'REGISTER', 'UNDO'} bl_description = "Makes hair guides from mesh edges." meshKind: StringProperty() targetHead = False headObj = 0 hairObjList = [] hairProxyList = [] @classmethod def poll(self, context): return(context.mode == 'OBJECT') def execute(self, context): error = 0 #0 = All good #1 = Hair guides have different lengths #2 = No seams in hair object #3 = Bevel on curve object targetObject = self.headObj for thisHairObj in self.hairObjList: options = [ 0, #0 the hair system's previous settings thisHairObj, #1 The hair object 0, #2 The hair system. So we don't have to rely on the selected system self.targetHead #3 Target a head object? ] #A new hair object gets a new guides list hairGuides = [] #if not self.targetHead: if thisHairObj.hnIsEmitter: targetObject = thisHairObj sysName = ''.join(["HN", thisHairObj.name]) if sysName in targetObject.particle_systems: #if this proxy object has an existing hair system on the target object, preserve its current settings if thisHairObj.hnMasterHairSystem == "": '''_TS Preserve and out''' options[0] = targetObject.particle_systems[sysName].settings options[2] = targetObject.particle_systems[sysName] else: '''TS Delete settings, copy, and out''' #Store a link to the system settings so we can delete the settings delSet = targetObject.particle_systems[sysName].settings #Get active_index of desired particle system bpy.context.object.particle_systems.active_index = bpy.context.object.particle_systems.find(sysName) #Delete Particle System removeParticleSystem(targetObject, targetObject.particle_systems[sysName]) #Delete Particle System Settings bpy.data.particles.remove(delSet) #Copy Hair settings from master. options[0] = bpy.data.particles[thisHairObj.hnMasterHairSystem].copy() options[2] = makeNewHairSystem(targetObject,sysName) else: #Create a new hair system if thisHairObj.hnMasterHairSystem != "": '''T_S copy, create new and out''' options[0] = bpy.data.particles[thisHairObj.hnMasterHairSystem].copy() # options[2] = self.headObj.particle_systems[sysName] '''_T_S create new and out''' options[2] = makeNewHairSystem(targetObject,sysName) if (self.meshKind=="SHEET"): print("Hair sheet "+ thisHairObj.name) #Create all hair guides #for hairObj in self.hairObjList: #Identify the seams and their vertices #Start looking here for multiple mesh problems. seamVerts, seamEdges, error = getSeams(thisHairObj) if(error == 0): vert_edges = edge_faces = False #For every vert in a seam, get the edge loop spawned by it for thisVert in seamVerts: edgeLoops, vert_edges, edge_faces = getLoops(thisHairObj, thisHairObj.data.vertices[thisVert], vert_edges, edge_faces, seamEdges) '''Is loopsToGuides() adding to the count of guides instead of overwriting?''' hairGuides = self.loopsToGuides(thisHairObj, edgeLoops, hairGuides) debPrintHairGuides(hairGuides) #Take each edge loop and extract coordinate data from its verts if (self.meshKind=="FIBER"): hairObj = self.hairObjList[0] print("Hair fiber") hairGuides = self.fibersToGuides(hairObj) if (self.meshKind=="CURVE"): #Preserve Active and selected objects tempActive = headObj = bpy.context.object tempSelected = [] tempSelected.append(bpy.context.selected_objects[0]) tempSelected.append(bpy.context.selected_objects[1]) #hairObj = bpy.context.selected_objects[0] hairObj = thisHairObj bpy.ops.object.select_all(action='DESELECT') if hairObj.data.bevel_object != None: error = 3 bpy.context.scene.objects.active=hairObj hairObj.select=True print("Curve Head: ", headObj.name) bpy.ops.object.convert(target='MESH', keep_original=True) fiberObj = bpy.context.active_object print("Hair Fibers: ", fiberObj.name) print("Hair Curves: ", hairObj.name) hairGuides = self.fibersToGuides(fiberObj) bpy.ops.object.delete(use_global=False) #Restore active object and selection bpy.context.scene.objects.active=tempActive bpy.ops.object.select_all(action='DESELECT') for sel in tempSelected: sel.select = True # return {'FINISHED'} if (self.checkGuides(hairGuides)): error = 1 #Process errors if error != 0: if error == 1: self.report(type = {'ERROR'}, message = "Mesh guides have different lengths") if error == 2: self.report(type = {'ERROR'}, message = ("No seams were defined in " + targetObject.name)) removeParticleSystem(targetObject, options[2]) if error == 3: self.report(type = {'ERROR'}, message = "Cannot create hair from curves with a bevel object") return{'CANCELLED'} #Subdivide hairs hairGuides = self.subdivideGuideHairs(hairGuides, thisHairObj) #Create the hair guides on the hair object self.createHair(targetObject, hairGuides, options) return {'FINISHED'} def invoke (self, context, event): self.headObj = bpy.context.object #Get a list of hair objects self.hairObjList = [] for obj in bpy.context.selected_objects: if obj != self.headObj or obj.hnIsEmitter: self.hairObjList.append(obj) #if the last object selected is not flagged as a self-emitter, then assume we are creating hair on a head #Otherwise, each proxy will grow its own hair if not self.headObj.hnIsEmitter: self.targetHead=True if len(bpy.context.selected_objects) < 2: self.report(type = {'ERROR'}, message = "Selection too small. Please select two objects") return {'CANCELLED'} else: self.targetHead=False return self.execute(context) def checkGuides(self, hairGuides): length = 0 for guide in hairGuides: if length == 0: length = len(guide) else: if length != len(guide): return 1 return 0 def createHair(self, ob, guides, options): debug = False tempActive = bpy.context.scene.objects.active bpy.context.scene.objects.active = ob if debug: print("Active Object: ", bpy.context.scene.objects.active.name) nGuides = len(guides) if debug: print("nGguides", nGuides) nSteps = len(guides[0]) if debug: print("nSteps", nSteps) # Create hair particle system if needed #bpy.ops.object.mode_set(mode='OBJECT') #bpy.ops.object.particle_system_add() psys = options[2] # Particle settings pset = psys.settings if options[0] != 0: #Use existing settings psys.settings = options[0] pset = options[0] else: #Create new settings pset.type = 'HAIR' pset.emit_from = 'FACE' pset.use_render_emitter = False pset.use_strand_primitive = True # Children pset.child_type = 'SIMPLE' pset.child_nbr = 6 pset.rendered_child_count = 50 pset.child_length = 1.0 pset.child_length_threshold = 0.0 pset.child_radius = 0.1 pset.child_roundness = 1.0 #Rename Hair Settings pset.name = ''.join([options[2].name, " Hair Settings"]) pset.hair_step = nSteps-1 #This set the number of guides for the particle system. It may have to be the same for every instance of the system. pset.count = nGuides #Render the emitter object? if options[3]: pset.use_render_emitter = True else: pset.use_render_emitter = False # Disconnect hair and switch to particle edit mode # Set all hair-keys # dt = 100.0/(nSteps-1) # dw = 1.0/(nSteps-1) # Connect hair to mesh # Segmentation violation during render if this line is absent. # Connecting hair moves the mesh points by an amount equal to the object's location bpy.ops.particle.particle_edit_toggle() bpy.context.scene.tool_settings.particle_edit.use_emitter_deflect = False bpy.context.scene.tool_settings.particle_edit.use_preserve_root = False bpy.context.scene.tool_settings.particle_edit.use_preserve_length = False bpy.ops.particle.disconnect_hair(all=True) #Connecting and disconnecting hair causes them to jump when other particle systems are created. bpy.ops.particle.connect_hair(all=True) for m in range(nGuides): #print("Working on guide #", m) nSteps = len(guides[m]) guide = guides[m] part = psys.particles[m] part.location = guide[0] #print("Guide #", m) for n in range(0, nSteps): point = guide[n] #print("Hair point #", n, ": ", point) h = part.hair_keys[n] #h.co_local = point h.co = point # print("#", n, " = ", point) # h.time = n*dt # h.weight = 1.0 - n*dw # Toggle particle edit mode bpy.ops.particle.particle_edit_toggle() bpy.context.scene.objects.active = tempActive return def createHairGuides(self, obj, edgeLoops): hairGuides = [] #For each loop for loop in edgeLoops: thisGuide = [] #For each vert in the loop for vert in loop[0]: thisGuide.append(obj.data.vertices[vert].co) hairGuides.append(thisGuide) return hairGuides def fibersToGuides(self, hairObj): guides = [] hairs = self.getHairsFromFibers(hairObj) for hair in hairs: guide = [] for vertIdx in hair: guide.append(hairObj.data.vertices[vertIdx].co.to_tuple()) guides.append(guide) return guides def getHairsFromFibers(self, hair): me = hair.data usedV = [] usedE = [] guides = [] # Create a dictionary with the vert index as key and edge-keys as value #It's a list of verts and the keys are the edges the verts belong to vert_edges = dict([(v.index, []) for v in me.vertices if v.hide!=1]) for ed in me.edges: for v in ed.key: if ed.key[0] in vert_edges and ed.key[1] in vert_edges: vert_edges[v].append(ed.key) #endPoints = dict([(v, []) for v in vert_edges if len(vert_edges[v])<2]) endPoints = [v for v in vert_edges if len(vert_edges[v])<2] #For every endpoint for vert in endPoints: hair=[] #print("first endpoint is ", vert) #check if EP has been used already in case it was a tail end already if vert not in usedV: #lookup the endpoint in vert_edges to get the edge(s) it's in thisEdge = getEdgeFromKey(me,vert_edges[vert][0]) #print("Got edge ", thisEdge) #Add the vert to the hair hair.append(vert) #mark the current vert as used #mark the current edge as used usedE.append(thisEdge) usedV.append(vert) #get the next/other vert in the edge #make it the current vert vert = getNextVertInEdge(thisEdge,vert) #print("got next vert ", vert, " edges", vert_edges[vert]) #while the number of edges the current vert is > 1 while len(vert_edges[vert])>1: #lookup the endpoint in vert_edges to get the edge(s) it's in thisEdge = getEdgeFromKey(me,vert_edges[vert][0]) if thisEdge in usedE: thisEdge = getEdgeFromKey(me,vert_edges[vert][1]) #Add the vert to the hair hair.append(vert) #mark the current vert as used #mark the current edge as used usedE.append(thisEdge) usedV.append(vert) #get the next/other vert in the edge #make it the current vert vert = getNextVertInEdge(thisEdge,vert) #print("vert #", vert) #print("edge #", thisEdge) #print(vert_edges[vert]) #Add the current vert to the hair hair.append(vert) #mark the current vert as used usedV.append(vert) #add the hair to the list of hairs guides.append(hair) #guides now holds a list of hairs where each hair is a list of vertex indices in the mesh "me" return guides def loopsToGuides(self, obj, edgeLoops, hairGuides): guides = hairGuides #guides = [] for loop in edgeLoops: hair = [] #hair is a list of coordinate sets. guides is a list of lists for vert in loop[0]: #co originally came through as a tuple. Is a Vector better? hair.append(obj.data.vertices[vert].co) # hair.append(obj.data.vertices[vert].co.to_tuple()) guides.append(hair) return guides def subdivideGuideHairs(self, guides, hairObj): debug = True #number of points in original guide hair hairLength = len(guides[0]) #original number of hairs numberHairs = len(guides) #number of hairs added between existing hairs hairSprouts = hairObj.hnSproutHairs #subdivide hairs if hairObj.hnSproutHairs > 0: #initialize an empty array so we don't have to think about inserting entries into lists. Check into this for later? newHairs = [[0 for i in range(hairLength)] for j in range(totalNumberSubdivisions(numberHairs, hairSprouts))] if debug: print ("Subdivide Hairs") newNumber = 1 #initial condition start = guides[0][0] newHairs[0][0] = start # debPrintHairGuides(newHairs) #for every hair pair, start at the root and send groups of four guide points to the interpolator #index identifies which row is current #kndex identifies the current hair in the list of new points #jndex identifies the current hair in the old list of hairs for index in range(0, hairLength): if debug: print("Hair Row ", index) #add the first hair's points newHairs[0][index] = guides[0][index] #Make a curve from the points in this row thisRow = [] for aHair in guides: thisRow.append(aHair[index]) curveObject = makePolyLine("rowCurveObj", "rowCurve", thisRow) for jndex in range(0, numberHairs-1): # knot1 = curveObject.data.splines[0].bezier_points[jndex] # knot2 = curveObject.data.splines[0].bezier_points[jndex + 1] knot1 = curveObject.splines[0].bezier_points[jndex] knot2 = curveObject.splines[0].bezier_points[jndex + 1] handle1 = knot1.handle_right handle2 = knot2.handle_left newPoints = mathutils.geometry.interpolate_bezier(knot1.co, handle1, handle2, knot2.co, hairSprouts+2) #add new points to the matrix #interpolate_bezier includes the endpoints so, for now, skip over them. re-write later to be a cleaner algorithm for kndex in range(0, len(newPoints)-2): newHairs[1+kndex+jndex*(1+hairSprouts)][index] = newPoints[kndex+1] # if debug: print("newHairs[", 1+kndex+jndex*(1+hairSprouts), "][", index, "] = ", newPoints[kndex], "SubD") # newHairs[jndex*(1+hairSprouts)][index] = newPoints[kndex] # print("knot1 = ", knot1) # print("knot2 = ", knot2) # print("newHairs[", 1+kndex+jndex*(1+hairSprouts), "][", index, "] = ", newPoints[kndex]) newNumber = newNumber + 1 #add the end point newHairs[(jndex+1)*(hairSprouts+1)][index] = guides[jndex+1][index] # if debug: print("newHairs[", (jndex+1)*(hairSprouts+1), "][", index, "] = ", guides[jndex][index], "Copy") newNumber = newNumber + 1 #clean up the curve we created bpy.data.curves.remove(curveObject) if debug: print("NewHairs") debPrintHairGuides(newHairs) guides = newHairs return guides class HairNetPanel(bpy.types.Panel): bl_idname = "PARTICLE_PT_HairNet" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "particle" bl_label = "HairNet 0.4.11" def draw(self, context): self.headObj = context.object #Get a list of hair objects self.hairObjList = context.selected_objects self.hairObjList.remove(self.headObj) layout = self.layout row = layout.row() row.label("Objects Start here") '''Is this a hair object?''' row = layout.row() try: # row.prop(self.headObj, 'hnIsHairProxy', text = "Hair Proxy") row.prop(self.headObj, 'hnIsEmitter', text = "Emit Hair on Self") except: pass #Draw this if this is a head object if not self.headObj.hnIsEmitter: box = layout.box() row = box.row() row.label(text="Hair Object:") row.label(text="Master Hair System:") for thisHairObject in self.hairObjList: row = box.row() row.prop_search(thisHairObject, 'hnMasterHairSystem', bpy.data, "particles", text = thisHairObject.name) row = box.row() row.label(text="Guide Subdivisions:") row.prop(thisHairObject, 'hnSproutHairs', text = "Subdivide U") # row.prop(thisHairObject, 'hnSubdivideHairSections', text = "Subdivide V") #Draw this if it's a self-emitter object else: box = layout.box() try: row = box.row() row.label(text="Master Hair System") row = box.row() row.prop_search(self.headObj, 'hnMasterHairSystem', bpy.data, "particles", text = self.headObj.name) except: pass row = box.row() row.label(text="Guide Subdivisions:") row.prop(self.headObj, 'hnSproutHairs', text = "Subdivide U") row = layout.row() row.operator("particle.hairnet", text="Add Hair From Sheets").meshKind="SHEET" row = layout.row() row.operator("particle.hairnet", text="Add Hair From Fibers").meshKind="FIBER" row = layout.row() row.operator("particle.hairnet", text="Add Hair From Curves").meshKind="CURVE" def register(): unregister_module(__name__) register_module(__name__) def unregister(): unregister_module(__name__) if __name__ == '__main__': register()