[
  {
    "path": ".gitignore",
    "content": "\n.project\n.pydevproject\nBlend Files/HairNet 2.8 Debug.blend\n*.blend\n*.blend1\nsrc/__pycache__/HairNet.cpython-37.pyc\nsrc/Run.py\nsrc/simp.py\n*.pyc\n.settings/org.eclipse.ltk.core.refactoring.prefs\nRelease/\nprev/__init__.py\nprev/hairNet.py\nprev/import_properties.py\nhairNet/Run.py\nhairNet/simp.py\nhairNet/hairNet.zip\n"
  },
  {
    "path": "README.md",
    "content": "# Attention:\nHairNet now works in Blender 2.80. \n\nThe buttons for the interface have moved to the Numeric Panel in the 3DView (N-key)\n\nHairNet for 2.79 still works.\n\n# HairNet\nHairNet addon for Blender\n\nBlender Wiki page:\nhttps://en.blender.org/index.php/Extensions:2.6/Py/Scripts/Objects/HairNet\n\nBlenderartists Thread:\nhttps://blenderartists.org/t/hair-guides-created-from-mesh-objects/572642\n\nInstallation:\nVisit the \"Releases\" page and download version 0.5.1.\nhttps://github.com/Jandals/HairNet/releases\n\nThen use Blender's \"User Preferences\" window to \"Install Addon From File.\" Choose the ZIP file you just downloaded and that should be all.\n"
  },
  {
    "path": "hairNet/Version Notes.txt",
    "content": "0.6.1\nupdated object selection call\n\n0.6.0\nUpdated many calls for Blender 2.80\nMoved interface to 3D_View to fix polling problem while switching to particle edit mode\nuses depsgraph to restore editing particle coordinates\n\n0.4.6\nFixed an issue which cause hairs to jump out of place when a second or third hair system is created. The solution was to remove the commands to connect and disconnect the hair system(s).\nChanged the behavior of Execute() to pass an existing hair system's name to createHair() so that users don't need to manually select the proper particle system.\n\n0.4.7\nProxy objects have a String tag which leads HairNet to a template hair system setting in the Blend File.\n\n0.4.8\nWe can now interpolate a hair mesh to create additional hairs in between those defined by the mesh. Next up is to interpolate each hair and subdivide it.\n\n0.4.9\nHair systems can now be applied to a proxy mesh as well as to a head object. The choice is made by how the objects are selected.\n\n0.4.10\nUndid the change to using operator overrides for creating particle systems. It was being done incorrectly and I can't find the proper way to do it.\n\n0.4.11\nRestored Making hair from curves. "
  },
  {
    "path": "hairNet/__init__.py",
    "content": "# ##### BEGIN GPL LICENSE BLOCK #####\n#\n#  This program is free software; you can redistribute it and/or\n#  modify it under the terms of the GNU General Public License\n#  as published by the Free Software Foundation; either version 2\n#  of the License, or (at your option) any later version.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License\n#  along with this program; if not, write to the Free Software Foundation,\n#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n#\n# ##### END GPL LICENSE BLOCK #####\n\nbl_info = {\n        \"name\":\"hairNet\",\n        \"author\": \"Rhett Jackson\",\n        \"version\": (0,6,6),\n        \"blender\": (2,90,0),\n        \"location\": \"Properties\",\n        \"category\": \"Particle\",\n        \"description\": \"Creates a particle hair system with hair guides from mesh edges which start at marked seams.\",\n        \"wiki_url\": \"http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Objects/HairNet\",\n        \"tracker_url\":\"http://projects.blender.org/tracker/index.php?func=detail&aid=35062&group_id=153&atid=467\"\n        }\n\n\nif \"bpy\" in locals():\n    import importlib\n    importlib.reload(hairNet)\n    importlib.reload(import_properties)\nelse:\n    from . import hairNet\n    from . import import_properties\n\nimport bpy\n\n\n# ### REGISTER ###\n\n\n\ndef register():\n\thairNet.register()\n\t#import_properties.register()\n\n\ndef unregister():\n\thairNet.unregister()\n\t#import_properties.unregister()\n\nif __name__ == \"__main__\":\n    register()\n"
  },
  {
    "path": "hairNet/hairNet.py",
    "content": "#---------------------------------------------------\n# File HairNet.py\n# Written by Rhett Jackson April 1, 2013\n# Some routines were copied from \"Curve Loop\" by Crouch https://sites.google.com/site/bartiuscrouch/scripts/curveloop\n# Some routines were copied from other sources\n# Very limited at this time:\n# 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.\n# NB 2) All meshes must have the same number of vertices in the direction that corresponds to hair growth\n#---------------------------------------------------\n\n\nimport bpy\nimport mathutils\nimport os\nimport traceback\n\n\nfrom mathutils import Vector\nfrom bpy.props import *\n\nfrom . import_properties import  *\n\nfrom pathlib import Path\n\nversionString = \"0.6.5\"\n\n#Start Debug\n\nhnDebFile = os.path.join(os.path.dirname(__file__), 'hairNetDeb.txt')\nif Path(hnDebFile).is_file():\n    print(\"HN Debug File Exists\")\n    import sys\n    pydev_path = '/Users/rhett/.p2/pool/plugins/org.python.pydev.core_7.7.0.202008021154/pysrc'\n    if sys.path.count(pydev_path) < 1: sys.path.append(pydev_path) \n    \n    import pydevd\n    \n    pydevd.settrace(stdoutToServer=True, stderrToServer=True, suspend=False)\nelse:\n    print(\"HN Debug File Doesn't exist\")\n    \n#End Debug\n\nclass UnionFindList: # a combination of unionfind and singlelinkedlist\n    def __init__(self, n):\n        self.parent = [ i for i in range(n) ]\n        self.next = [ -1 for _ in range(n) ]\n        self.rank = [ 1 for _ in range(n) ]\n    def findRoots(self):\n        return [ i for i, v in enumerate(self.parent) if i == v and self.rank[i] != 1 ] # ignore single point\n    def findRoot(self, x):\n        if self.parent[x] == x:\n            return x\n        self.parent[x] = self.findRoot(self.parent[x])\n        return self.parent[x]\n    def getNext(self, x): # return -1 if no next\n        return self.next[x]\n    def getChainLength(self, x): # x must be the root\n        return self.rank[x]\n    def getChain(self, x):\n        ret = []\n        x_next = x\n        while x_next != -1:\n            ret.append(x_next)\n            x_next = self.next[x_next]\n        return ret\n    def reverseChain(self, x_root, x): # x_root is the head of list, x is the end of list\n        if self.rank[x_root] == 1: return\n        x_pre = -1\n        x_cur = x_root\n        while x_cur != -1:\n            x_next = self.next[x_cur]\n            self.next[x_cur] = x_pre\n            self.parent[x_cur] = x # parent of all nodes should be x after reversing\n            x_pre = x_cur\n            x_cur = x_next\n        self.rank[x] = self.rank[x_root]\n    def union(self, x, y):\n        x_root = self.findRoot(x)\n        y_root = self.findRoot(y)\n        if x_root == y_root: # already connected\n            return\n            \n        if y_root != y and x_root != x: \n            # Case 1: two root points are independent, should reverse one of chains, choose shorter chain to reverse\n            # and transform this situation to Case 2\n            if self.rank[x_root] <= self.rank[y_root]:\n                self.reverseChain(x_root, x)\n                x_root = x\n            else:\n                self.reverseChain(y_root, y)\n                y_root = y\n                \n        if y_root == y: # Case 2: one of two points is dependent or all two points are dependent\n            if self.next[x] != -1:\n                self.parent[x] = y\n                self.next[y] = x\n                self.rank[y] = self.rank[y] + self.rank[x] # y become new root\n            else:\n                self.parent[y] = x_root\n                self.next[x] = y\n                self.rank[x_root] = self.rank[x_root] + self.rank[y]\n        elif x_root == x:\n            if self.next[y] != -1:\n                self.parent[y] = x \n                self.next[x] = y \n                self.rank[x] = self.rank[x] + self.rank[y] # x become new root\n            else:\n                self.parent[x] = y_root\n                self.next[y] = x\n                self.rank[y_root] = self.rank[y_root] + self.rank[x]\n\n# It is always good to use wrapper prop when attacking to common data block such as Object to reduce blend junk\nclass HairNetConfig(PropertyGroup):\n    masterHairSystem: StringProperty(\n        name=\"hnMasterHairSystem\",\n        description=\"Name of the hair system to be copied by this proxy object.\",\n        default=\"\")\n    \n    isHairProxy: BoolProperty(\n            name=\"hnIsHairProxy\",\n            description=\"Is this object a hair proxy object?\",\n            default=False)\n\n    isEmitter: BoolProperty(\n            name=\"hnIsEmitter\",\n            description=\"Is this object a hair emitter object?\",\n            default=False)\n\n    sproutHairs: IntProperty(\n            name=\"hnSproutHairs\",\n            description=\"Number of additional hairs to add.\",\n            default=0)\n\n    # subdivideHairSections: IntProperty(\n    #         name=\"hnSubdivideHairSections\",\n    #         description=\"Number of subdivisions to add along the guide hairs\",\n    #         default=0)\n\ndef debPrintVertEdges(vert_edges):\n    print(\"vert_edges: \")\n    for vert in vert_edges:\n        print(vert, \": \", vert_edges[vert])\n\ndef debPrintEdgeFaces(edge_faces):\n    print(\"edge_faces: \")\n    for edge in edge_faces:\n        print(edge, \": \", edge_faces[edge])\n\ndef debPrintEdgeKeys(edges):\n    for edge in edges:\n        print(edge, \" : \", edge.key)\n\ndef debPrintHairGuides(hairGuides):\n    print(\"Hair Guides:\")\n    guideN=0\n\n    for group in hairGuides:\n        print(\"Guide #\",guideN)\n        i=0\n        for guide in group:\n            print(i, \" : \", guide)\n            i += 1\n        guideN+=1\n\ndef debPrintSeams(seamVerts, seamEdges):\n    print(\"Verts in the seam: \")\n    for vert in seamVerts:\n        print(vert)\n    print(\"Edges in the seam: \")\n    for edge in seamEdges:\n        print(edge.key)\n\ndef debPrintLoc(func=\"\"):\n    obj = bpy.context.object\n    print(obj.name, \" \", func)\n    print(\"Coords\", obj.data.vertices[0].co)\n\ndef getEdgeFromKey(mesh,key):\n    v1 = key[0]\n    v2 = key[1]\n    theEdge = 0\n    for edge in mesh.edges:\n        if v1 in edge.vertices and v2 in edge.vertices:\n            #print(\"Found edge :\", edge.index)\n            return edge\n    return 0\n\n# returns all edge loops that a vertex is part of\ndef getLoops(obj, v1, vert_edges, edge_faces, seamEdges):\n    debug = False\n\n    me = obj.data\n    if not vert_edges:\n        # Create a dictionary with the vert index as key and edge-keys as value\n        #It's a list of verts and the keys are the edges the verts belong to\n        vert_edges = dict([(v.index, []) for v in me.vertices if v.hide!=1])\n        for ed in me.edges:\n            for v in ed.key:\n                if ed.key[0] in vert_edges and ed.key[1] in vert_edges:\n                    vert_edges[v].append(ed.key)\n        if debug: debPrintVertEdges(vert_edges)\n    if not edge_faces:\n        # Create a dictionary with the edge-key as key and faces as value\n        # It's a list of edges and the faces they belong to\n        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)])\n        for f in me.polygons:\n            for key in f.edge_keys:\n                if key in edge_faces and f.hide!=1:\n                    edge_faces[key].append(f.index)\n        if debug : debPrintEdgeFaces(edge_faces)\n\n    ed_used = [] # starting edges that are already part of a loop that is found\n    edgeloops = [] # to store the final results in\n    for ed in vert_edges[v1.index]: #ed is all the edges v1 is a part of\n        if ed in ed_used:\n            continue\n        seamTest = getEdgeFromKey(me, ed)\n        if seamTest.use_seam:\n            #print(\"Edge \", seamTest.index, \" is a seam\")\n            continue\n\n        vloop = [] # contains all verts of the loop\n        poles = [] # contains the poles at the ends of the loop\n        circle = False # tells if loop is circular\n        n = 0 # to differentiate between the start and the end of the loop\n\n        for m in ed: # for each vert in the edge\n            n+=1\n            active_ed = ed\n            active_v  = m\n            if active_v not in vloop:\n                vloop.insert(0,active_v)\n            else:\n                break\n            stillGrowing = True\n            while stillGrowing:\n                stillGrowing = False\n                active_f = edge_faces[active_ed] #List of faces the edge belongs to\n                new_ed = vert_edges[active_v] #list of edges the vert belongs to\n                if len(new_ed)<3: #only 1 or 2 edges\n                    break\n                if len(new_ed)>4: #5-face intersection\n                    # detect poles and stop growing\n                    if n>1:\n                        poles.insert(0,vloop.pop(0))\n                    else:\n                        poles.append(vloop.pop(-1))\n                    break\n                for i in new_ed: #new_ed - must have 3 or 4 edges coming from the vert\n                    eliminate = False # if edge shares face, it has to be eliminated\n                    for j in edge_faces[i]: # j is one of the face indices in edge_faces\n                        if j in active_f:\n                            eliminate = True\n                            break\n                    if not eliminate: # it's the next edge in the loop\n                        stillGrowing = True\n                        active_ed = i\n                        if active_ed in vert_edges[v1.index]: #the current edge contains v1\n\n                            ed_used.append(active_ed)\n                        for k in active_ed:\n                            if k != active_v:\n                                if k not in vloop:\n\n                                    if n>1:\n                                        vloop.insert(0,k)\n                                    else:\n                                        vloop.append(k)\n\n\n                                    active_v = k\n                                    break\n                                else:\n                                    stillGrowing = False # we've come full circle\n                                    circle = True\n                        break\n        #TODO: Function to sort vloop. Use v1 and edge data to walk the ring in order\n        vloop = sortLoop(obj, vloop, v1, seamEdges, vert_edges)\n        edgeloops.append([vloop, poles, circle])\n    for loop in edgeloops:\n        for vert in loop[0]:\n            me.vertices[vert].select=True\n            #me.edges[edge].select=True\n    return edgeloops, vert_edges, edge_faces\n\n\n\n\ndef getSeams(obj):\n    debug = False\n    #Make a list of all edges marked as seams\n    error = 0\n    seamEdges = []\n    for edge in obj.data.edges:\n        if edge.use_seam:\n            seamEdges.append(edge)\n\n    #Sort the edges in seamEdges\n#     seamEdges = sortEdges(seamEdges)\n\n    #Make a list of all verts in the seam\n    seamVerts = []\n    for edge in seamEdges:\n        for vert in edge.vertices:\n            if vert not in seamVerts:\n                seamVerts.append(vert)\n\n    if(len(seamEdges) < 2):\n        error = 2\n        return 0, 0, error\n\n    seamVerts = sortSeamVerts(seamVerts, seamEdges)\n    if debug: debPrintSeams(seamVerts, seamEdges)\n\n    if(len(seamEdges) == 0):\n        error = 2\n\n    return seamVerts, seamEdges, error\n\ndef getNextVertInEdge(edge, vert):\n    if vert == edge.vertices[0]:\n        return edge.vertices[1]\n    else:\n        return edge.vertices[0]\n\ndef makeNewHairSystem(headObject,systemName):\n    bpy.ops.object.mode_set(mode='OBJECT')\n    #Adding a particle modifier also works but requires pushing/pulling the active object and selection.\n    headObject.modifiers.new(\"HairNet\", 'PARTICLE_SYSTEM')\n\n    #Set up context override\n#    override = {\"object\": headObject, \"particle_system\": systemName}\n#    bpy.ops.object.particle_system_add(override)\n    headObject.particle_systems[-1].name = systemName\n    headObject.particle_systems[-1].settings.type = 'HAIR'\n    headObject.particle_systems[-1].settings.render_step = 5\n    return headObject.particle_systems[systemName]\n\ndef makePolyLine(objName, curveName, cList):\n    #objName and curveName are strings cList is a list of vectors\n    curveData = bpy.data.curves.new(name=curveName, type='CURVE')\n    curveData.dimensions = '3D'\n\n#     objectData = bpy.data.objects.new(objName, curveData)\n#     objectData.location = (0,0,0) #object origin\n#     bpy.context.scene.objects.link(objectData)\n\n    polyline = curveData.splines.new('BEZIER')\n    polyline.bezier_points.add(len(cList)-1)\n    for num in range(len(cList)):\n        x, y, z = cList[num]\n        polyline.bezier_points[num].co = (x, y, z)\n        polyline.bezier_points[num].handle_left_type = polyline.bezier_points[num].handle_right_type = \"AUTO\"\n\n#     return objectData\n    return curveData\n\ndef preserveSelection():\n    #Preserve Active and selected objects\n    storedActive = bpy.context.object\n    storedSelected = []\n    for sel in bpy.context.selected_objects:\n        storedSelected.append(sel)\n\n    return storedActive, storedSelected\n\n\n\n\ndef changeSelection(thisObject):\n    storedActive, storedSelected = preserveSelection()\n\n    bpy.ops.object.select_all(action='DESELECT')\n    bpy.context.view_layer.objects.active=thisObject\n    thisObject.select_set(state=True)\n    return storedActive, storedSelected\n\ndef restoreSelection(storedActive, storedSelected):\n    #Restore active object and selection\n    bpy.context.view_layer.objects.active=storedActive\n    bpy.ops.object.select_all(action='DESELECT')\n    for sel in storedSelected:\n        sel.select = True\n\ndef removeParticleSystem(object, particleSystem):\n    override = {\"object\": object, \"particle_system\": particleSystem}\n    bpy.ops.object.particle_system_remove(override)\n\n\ndef sortEdges(edgesList):\n    sorted = []\n    debPrintEdgeKeys(edgesList)\n\n    return edgesList\n\ndef sortLoop(obj, vloop, v1, seamEdges, vert_edges):\n    #The hair is either forward or reversed. If it's reversed, reverse it again. Otherwise do nothing.\n    loop = []\n    loopRange = len(vloop)-1\n\n    if vloop[0] == v1.index:\n        loop = vloop.copy()\n\n    else:\n        loop = vloop[::-1]\n    return loop\n\ndef sortSeamVerts(verts, edges):\n\n    debug = False\n    sortedVerts = []\n    usedEdges = []\n    triedVerts = []\n    triedEdges = []\n    startingVerts = []\n\n    #Make a list of starting points so that each island will have a starting point. Make another \"used edges\" list\n\n    def findEndpoint(vert):\n        for thisVert in verts:\n            count = 0\n            if thisVert not in triedVerts:\n                triedVerts.append(thisVert)\n                #get all edges with thisVert in it\n                all_edges = [e for e in edges if thisVert in e.vertices]\n\n                if len(all_edges) == 1:\n                    #The vert is in only one edge and is thus an endpoint\n                    startingVerts.append(thisVert)\n                    #walk to the other end of the seam and add verts to triedVerts\n                    walking = True\n                    thatVert = thisVert\n                    beginEdge = thatEdge = all_edges[0]\n                    while walking:\n                        #get the other vert in the edge\n                        if thatVert == thatEdge.key[0]:\n                            thatVert = thatEdge.key[1]\n                        else:\n                            thatVert = thatEdge.key[0]\n                        #Add current edge to triedEdges\n                        triedEdges.append(thatEdge)\n                        if thatVert not in triedVerts: triedVerts.append(thatVert)\n                        #Put next edge in thatEdge\n                        nextEdge = [e for e in edges if thatVert in e.vertices and e not in triedEdges]\n                        if len(nextEdge) == 1:\n                            #This means one edge was found that wasn't already used\n                            thatEdge = nextEdge[0]\n                        else:\n                            #No unused edges were found\n                            walking = False\n\n    #                 break\n        #at this point, we have found an endpoint\n        if debug:\n            print(\"seam endpoint\", thisVert)\n            print(\"ending edge\", beginEdge.key)\n        #get the edge the vert is in\n        #for thisEdge in edges:\n        return beginEdge, thisVert\n\n    for aVert in verts:\n        if aVert not in triedVerts:\n            thisEdge, thisVert = findEndpoint(aVert)\n\n    #Now, walk through the edges to put the verts in the right order\n\n    for thisVert in startingVerts:\n        thisEdge = [x for x in edges if (thisVert in x.key)][0]\n        sortedVerts.append(thisVert)\n        keepRunning = True\n        while keepRunning:\n            for newVert in thisEdge.key:\n                if debug: print(\"next vert is #\", newVert)\n                if thisVert != newVert:\n                    #we have found the other vert if this edge\n                    #store it and find the next edge\n                    thisVert = newVert\n                    sortedVerts.append(thisVert)\n                    usedEdges.append(thisEdge)\n                    break\n            try:\n                thisEdge = [x for x in edges if ((thisVert in x.key) and (x not in usedEdges))][0]\n            except:\n                keepRunning = False\n            if debug: print(\"next vert is in edge\", thisEdge.key)\n\n\n\n\n    return sortedVerts\n\n\n\ndef totalNumberSubdivisions(points, cuts):\n    return points + (points - 1)*cuts\n\nmesh_kinds=[\n    (\"SHEET\", \"Sheets\",\"Create hair from sheets\"),\n    (\"FIBER\", \"Fibermesh\",\"Create hair from loose edges\"),\n    (\"CURVE\", \"Curves\",\"Create hair from curve splines\")\n]\n\nclass HAIRNET_OT_operator (bpy.types.Operator):\n    bl_idname = \"hairnet.operator\"\n    bl_label = \"HairNet\"\n    bl_options = {\"REGISTER\", 'UNDO'}\n    bl_description = \"Makes hair guides from mesh edges.\"\n\n    meshKind : EnumProperty(items=mesh_kinds, name=\"Generator kind\", default=\"FIBER\")\n    \n    targetHead = False\n    headObj = 0\n    hairObjList = []\n    hairProxyList = []\n    \n    @classmethod\n    def poll(self, context):\n        return(context.mode == 'OBJECT')\n\n    def execute(self, context):\n        debug = False\n        error = 0   #0 = All good\n                    #1 = Hair guides have different lengths\n                    #2 = No seams in hair object\n                    #3 = Bevel on curve object\n\n        targetObject = self.headObj\n\n        for thisHairObj in self.hairObjList:\n            options = [\n                       0,                   #0 the hair system's previous settings\n                       thisHairObj,         #1 The hair object\n                       0,                   #2 The hair system. So we don't have to rely on the selected system\n                       self.targetHead,      #3 Target a head object?\n                       targetObject,         #4 targetObject\n                       \"name\"               #5 particle system name\n                       ]\n\n            #Get dependency graph\n            \"\"\"depsgraph = bpy.context.evaluated_depsgraph_get()\n            thisHairObj = thisHairObj.evaluated_get(depsgraph)\n            options[1] = thisHairObj\"\"\"\n            \n            #A new hair object gets a new guides list\n            hairGuides = []\n\n            #if not self.targetHead:\n            if thisHairObj.hn_cfg.isEmitter:\n                targetObject = thisHairObj\n                \n            #targetObject = targetObject.evaluated_get(depsgraph)\n\n            #targetObject = targetObject.evaluated_get(depsgraph)\n            \n            config=thisHairObj.hn_cfg\n            \n            sysName = ''.join([\"HN\", thisHairObj.name])\n            options[5] = sysName\n\n            if sysName in targetObject.particle_systems:\n                #if this proxy object has an existing hair system on the target object, preserve its current settings\n                if config.masterHairSystem == \"\":\n                    '''_TS Preserve and out'''\n                    options[0] = targetObject.particle_systems[sysName].settings\n                    options[2] = targetObject.particle_systems[sysName]\n\n                else:\n                    '''TS Delete settings, copy, and out'''\n                    #Store a link to the system settings so we can delete the settings\n                    delSet = targetObject.particle_systems[sysName].settings\n                    #Get active_index of desired particle system\n                    bpy.context.object.particle_systems.active_index = bpy.context.object.particle_systems.find(sysName)\n                    #Delete Particle System\n                    removeParticleSystem(targetObject, targetObject.particle_systems[sysName])\n                    #Delete Particle System Settings\n                    bpy.data.particles.remove(delSet)\n                    #Copy Hair settings from master.\n                    options[0] = bpy.data.particles[config.masterHairSystem].copy()\n\n                    options[2] = makeNewHairSystem(targetObject,sysName)\n            else:\n                #Create a new hair system\n                if config.masterHairSystem != \"\":\n                    '''T_S copy, create new and out'''\n                    options[0] = bpy.data.particles[config.masterHairSystem].copy()\n#                     options[2] = self.headObj.particle_systems[sysName]\n\n                '''_T_S create new and out'''\n                options[2] = makeNewHairSystem(targetObject,sysName)\n\n            if (self.meshKind==\"SHEET\"):\n                if debug: print(\"Hair sheet \"+ thisHairObj.name)\n                #Create all hair guides\n                #for hairObj in self.hairObjList:\n                #Identify the seams and their vertices\n                #Start looking here for multiple mesh problems.\n                seamVerts, seamEdges, error = getSeams(thisHairObj)\n\n                if(error == 0):\n                    vert_edges = edge_faces = False\n                    #For every vert in a seam, get the edge loop spawned by it\n                    for thisVert in seamVerts:\n                        edgeLoops, vert_edges, edge_faces = getLoops(thisHairObj, thisHairObj.data.vertices[thisVert], vert_edges, edge_faces, seamEdges)\n                        '''Is loopsToGuides() adding to the count of guides instead of overwriting?'''\n                        hairGuides = self.loopsToGuides(thisHairObj, edgeLoops, hairGuides)\n                    if debug: debPrintHairGuides(hairGuides)\n                    #Take each edge loop and extract coordinate data from its verts\n\n            if (self.meshKind==\"FIBER\"):\n                hairObj = thisHairObj\n                if debug: print(\"Hair fiber\")\n                hairGuides = self.fibersToGuides(hairObj)\n\n            if (self.meshKind==\"CURVE\"):\n                #Preserve Active and selected objects\n                tempActive = headObj = bpy.context.object\n                tempSelected = []\n                tempSelected.append(bpy.context.selected_objects[0])\n                tempSelected.append(bpy.context.selected_objects[1])\n                #hairObj = bpy.context.selected_objects[0]\n                hairObj = thisHairObj\n                bpy.ops.object.select_all(action='DESELECT')\n                \n                if hairObj.data.bevel_depth > 0.0:\n                    error = 3\n\n                bpy.context.view_layer.objects.active=hairObj\n                hairObj.select_set(state=True)\n\n                if debug: print(\"Curve Head: \", headObj.name)\n                bpy.ops.object.convert(target='MESH', keep_original=True)\n                fiberObj = bpy.context.active_object\n\n                if debug:\n                    print(\"Hair Fibers: \", fiberObj.name)\n                    print(\"Hair Curves: \", hairObj.name)\n\n                hairGuides = self.fibersToGuides(fiberObj)\n\n                bpy.ops.object.delete(use_global=False)\n\n                #Restore active object and selection\n                bpy.context.view_layer.objects.active=tempActive\n                bpy.ops.object.select_all(action='DESELECT')\n                for sel in tempSelected:\n                    sel.select_set(state=True)\n    #            return {'FINISHED'}\n\n            if (self.checkGuides(hairGuides)):\n                error = 1\n\n            #Process errors\n            if error != 0:\n                if error == 1:\n                    self.report(type = {'ERROR'}, message = \"Mesh guides have different lengths\")\n                if error == 2:\n                    self.report(type = {'ERROR'}, message = (\"No seams were defined in \" + targetObject.name))\n                    removeParticleSystem(targetObject, options[2])\n                if error == 3:\n                    self.report(type = {'ERROR'}, message = \"Cannot create hair from curves with a bevel object\")\n                return{'CANCELLED'}\n\n            #Subdivide hairs\n            hairGuides = self.subdivideGuideHairs(hairGuides, thisHairObj)\n\n            #Create the hair guides on the hair object\n            self.createHair(targetObject, hairGuides, options)\n\n        return {'FINISHED'}\n\n    def invoke (self, context, event):\n\n        self.headObj = bpy.context.object\n\n        #Get a list of hair objects\n        self.hairObjList = []\n        for obj in bpy.context.selected_objects:\n            if obj != self.headObj or obj.hn_cfg.isEmitter:\n                self.hairObjList.append(obj)\n\n\n        #if the last object selected is not flagged as a self-emitter, then assume we are creating hair on a head\n        #Otherwise, each proxy will grow its own hair\n\n        if not self.headObj.hn_cfg.isEmitter:\n            self.targetHead=True\n            if len(bpy.context.selected_objects) < 2:\n                self.report(type = {'ERROR'}, message = \"Selection too small. Please select two objects\")\n                return {'CANCELLED'}\n        else:\n            self.targetHead=False\n\n\n\n\n        return self.execute(context)\n\n    def checkGuides(self, hairGuides):\n        length = 0\n        for guide in hairGuides:\n            if length == 0:\n                length = len(guide)\n            else:\n                if length != len(guide):\n                    return 1\n        return 0\n\n    def createHair(self, ob, guides, options):\n        debug = False\n        \n        tempActive = bpy.context.active_object\n        bpy.context.view_layer.objects.active = ob\n        \n        if debug: print(\"Active Object: \", bpy.context.active_object.name)\n\n        nGuides = len(guides)\n        if debug: print(\"nGguides\", nGuides)\n        nSteps = len(guides[0])\n        if debug: print(\"nSteps\", nSteps)\n\n        # Create hair particle system if  needed\n        #bpy.ops.object.mode_set(mode='OBJECT')\n        #bpy.ops.object.particle_system_add()\n\n        psys = options[2]\n\n        # Particle settings\n        pset = psys.settings\n\n        if options[0] != 0:\n            #Use existing settings\n            psys.settings = options[0]\n            pset = options[0]\n        else:\n            #Create new settings\n            #pset.type = 'HAIR'\n            pset.emit_from = 'FACE'\n            ob.show_instancer_for_render = False\n            pset.use_strand_primitive = True\n\n            # Children\n            pset.child_type = 'SIMPLE'\n            pset.child_nbr = 6\n            pset.rendered_child_count = 50\n            pset.child_length = 1.0\n            pset.child_length_threshold = 0.0\n            pset.child_radius = 0.1\n            pset.child_roundness = 1.0\n\n        #Rename Hair Settings\n        #pset.name = ''.join([options[2].name, \" Hair Settings\"])\n        pset.hair_step = nSteps-1\n        #This set the number of guides for the particle system. It may have to be the same for every instance of the system.\n        pset.count = nGuides\n\n        #Render the emitter object?\n        if options[3]:\n            ob.show_instancer_for_render = True\n        else:\n            ob.show_instancer_for_render = False\n    \n        # Disconnect hair and switch to particle edit mode\n        # Connect hair to mesh\n        # Segmentation violation during render if this line is absent.\n        # Connecting hair moves the mesh points by an amount equal to the object's location\n        \n        bpy.ops.particle.particle_edit_toggle()\n\n        #bpy.context.scene.tool_settings.particle_edit.tool = 'COMB'\n        bpy.ops.particle.brush_edit(stroke=[{'name': '', 'location': (0, 0, 0), 'mouse': (0, 0), 'mouse_event':(0, 0), 'pressure': 0, 'size': 0, 'pen_flip': False,  \"x_tilt\":0, \"y_tilt\":0, 'time': 0, 'is_start': False}])\n        bpy.ops.particle.particle_edit_toggle()\n        bpy.context.scene.tool_settings.particle_edit.use_emitter_deflect = False\n        bpy.context.scene.tool_settings.particle_edit.use_preserve_root = False\n        bpy.context.scene.tool_settings.particle_edit.use_preserve_length = False\n        \n        bpy.ops.particle.disconnect_hair(all=True)\n        #Connecting and disconnecting hair causes them to jump when other particle systems are created.\n        bpy.ops.particle.connect_hair(all=True)\n\n        targetObj = options[4]\n        \n        depsgraph = bpy.context.evaluated_depsgraph_get()\n        depObj = targetObj.evaluated_get(depsgraph)\n        psys = depObj.particle_systems[options[5]]\n    \n        for m in range(0, nGuides):\n            #print(\"Working on guide #\", m)\n            nSteps = len(guides[m])\n            guide = guides[m]\n            part = psys.particles[m]\n            part.location = guide[0]\n\n            #print(\"Guide #\", m)\n            for n in range(0, nSteps):\n                point = guide[n]\n                #print(\"Hair point #\", n, \": \", point)\n                h = part.hair_keys[n]\n                #h.co_local = point\n                h.co = point\n                #print(\"h.co = \", h.co)\n                \n        # Toggle particle edit mode\n        bpy.ops.particle.particle_edit_toggle()\n        bpy.ops.particle.particle_edit_toggle()\n        \n        bpy.context.view_layer.objects.active = tempActive\n        return\n\n    def createHairGuides(self, obj, edgeLoops):\n        hairGuides = []\n\n        #For each loop\n        for loop in edgeLoops:\n            thisGuide = []\n            #For each vert in the loop\n            for vert in loop[0]:\n                thisGuide.append(obj.data.vertices[vert].co)\n            hairGuides.append(thisGuide)\n\n        return hairGuides\n\n    def fibersToGuides(self, hairObj):\n        import time # evaluation\n        time_start = time.time()\n\n        me = hairObj.data\n        uf = UnionFindList(len(me.vertices))\n        for ed in me.edges:\n            # the edge wouldn't exist if one of points is hidden\n            if me.vertices[ed.key[0]].hide == True or me.vertices[ed.key[1]].hide == True: continue\n            uf.union(ed.key[0], ed.key[1])\n\n        ret = [ [ hairObj.data.vertices[vertIdx].co.to_tuple() for vertIdx in uf.getChain(vert) ] for vert in uf.findRoots() ]\n\n        time_end = time.time()\n        print(\"Function getHairsFromFibers cost:\", time_end-time_start)\n        #default cost: 139.5683515071869 # 33,192 points\n        #now cost: 0.5224146842956543 # 33,192 points\n        #now cost: 1.9567747116088867 # 137,238 points\n\n        return ret\n\n    def loopsToGuides(self, obj, edgeLoops, hairGuides):\n        guides = hairGuides\n        #guides = []\n\n        for loop in edgeLoops:\n            hair = []\n            #hair is a list of coordinate sets. guides is a list of lists\n            for vert in loop[0]:\n                #co originally came through as a tuple. Is a Vector better?\n                hair.append(obj.data.vertices[vert].co)\n    #             hair.append(obj.data.vertices[vert].co.to_tuple())\n            guides.append(hair)\n        return guides\n\n    def subdivideGuideHairs(self, guides, hairObj):\n        debug = True\n        #number of points in original guide hair\n        hairLength = len(guides[0])\n\n        #original number of hairs\n        numberHairs = len(guides)\n\n        #number of hairs added between existing hairs\n        hairSprouts = hairObj.hn_cfg.sproutHairs\n\n        #subdivide hairs\n        if hairObj.hn_cfg.sproutHairs > 0:\n            #initialize an empty array so we don't have to think about inserting entries into lists. Check into this for later?\n            newHairs = [[0 for i in range(hairLength)] for j in range(totalNumberSubdivisions(numberHairs, hairSprouts))]\n            if debug: print (\"Subdivide Hairs\")\n            newNumber = 1\n\n            #initial condition\n            start = guides[0][0]\n            newHairs[0][0] = start\n    #         debPrintHairGuides(newHairs)\n            #for every hair pair, start at the root and send groups of four guide points to the interpolator\n            #index identifies which row is current\n            #kndex identifies the current hair in the list of new points\n            #jndex identifies the current hair in the old list of hairs\n            for index in range(0, hairLength):\n                if debug: print(\"Hair Row \", index)\n                #add the first hair's points\n                newHairs[0][index] = guides[0][index]\n                #Make a curve from the points in this row\n                thisRow = []\n                for aHair in guides:\n                    thisRow.append(aHair[index])\n                curveObject = makePolyLine(\"rowCurveObj\", \"rowCurve\", thisRow)\n                for jndex in range(0, numberHairs-1):\n    #                 knot1 = curveObject.data.splines[0].bezier_points[jndex]\n    #                 knot2 = curveObject.data.splines[0].bezier_points[jndex + 1]\n                    knot1 = curveObject.splines[0].bezier_points[jndex]\n                    knot2 = curveObject.splines[0].bezier_points[jndex + 1]\n                    handle1 = knot1.handle_right\n                    handle2 = knot2.handle_left\n                    newPoints = mathutils.geometry.interpolate_bezier(knot1.co, handle1, handle2, knot2.co, hairSprouts+2)\n\n\n                    #add new points to the matrix\n                    #interpolate_bezier includes the endpoints so, for now, skip over them. re-write later to be a cleaner algorithm\n                    for kndex in range(0, len(newPoints)-2):\n                        newHairs[1+kndex+jndex*(1+hairSprouts)][index] = newPoints[kndex+1]\n    #                     if debug: print(\"newHairs[\", 1+kndex+jndex*(1+hairSprouts), \"][\", index, \"] = \", newPoints[kndex], \"SubD\")\n    #                     newHairs[jndex*(1+hairSprouts)][index] = newPoints[kndex]\n    #                     print(\"knot1 = \", knot1)\n    #                     print(\"knot2 = \", knot2)\n    #                     print(\"newHairs[\", 1+kndex+jndex*(1+hairSprouts), \"][\", index, \"] = \", newPoints[kndex])\n                        newNumber = newNumber + 1\n\n\n                    #add the end point\n                    newHairs[(jndex+1)*(hairSprouts+1)][index] = guides[jndex+1][index]\n    #                 if debug: print(\"newHairs[\", (jndex+1)*(hairSprouts+1), \"][\", index, \"] = \", guides[jndex][index], \"Copy\")\n                    newNumber = newNumber + 1\n\n                #clean up the curve we created\n                bpy.data.curves.remove(curveObject)\n            if debug:\n                print(\"NewHairs\")\n                debPrintHairGuides(newHairs)\n            guides = newHairs\n\n        return guides\n\nclass HAIRNET_PT_panel(bpy.types.Panel):\n    bl_idname = \"HAIRNET_PT_HairNet\"\n    bl_space_type = \"PROPERTIES\"\n    bl_region_type = \"WINDOW\"\n    bl_context = \"particle\"\n    bl_label = \"HairNet \" + versionString\n    \n\n\n    def draw(self, context):\n\n        self.headObj = context.object\n\n        #Get a list of hair objects\n        self.hairObjList = context.selected_objects\n        if self.headObj in self.hairObjList:\n            self.hairObjList.remove(self.headObj)\n\n        layout = self.layout\n\n        row = layout.row()\n        row.label(text = \"Objects Start here\")\n\n        #Is this a hair object?\n\n        row = layout.row()\n        try:\n            row.prop(self.headObj.hn_cfg, 'isEmitter', text = \"Emit Hair on Self\")\n        except:\n            pass\n\n        #Draw this if this is a head object\n        if not self.headObj.hn_cfg.isEmitter:\n            box = layout.box()\n            row = box.row()\n            row.label(text = \"Hair Object:\")\n            row.label(text = \"Master Hair System:\")\n            for thisHairObject in self.hairObjList:\n                row = box.row()\n                row.prop_search(thisHairObject.hn_cfg, 'masterHairSystem',  bpy.data, \"particles\", text = thisHairObject.name)\n                row = box.row()\n                row.label(text = \"Guide Subdivisions:\")\n                row.prop(thisHairObject.hn_cfg, 'sproutHairs', text = \"Subdivide U\")\n#                 row.prop(thisHairObject, 'hnSubdivideHairSections', text = \"Subdivide V\")\n\n        #Draw this if it's a self-emitter object\n        else:\n            box = layout.box()\n            try:\n                row = box.row()\n                row.label(text = \"Master Hair System\")\n                row = box.row()\n                row.prop_search(self.headObj.hn_cfg, 'masterHairSystem',  bpy.data, \"particles\", text = self.headObj.name)\n\n            except:\n                pass\n            row = box.row()\n            row.label(text = \"Guide Subdivisions:\")\n            row.prop(self.headObj.hn_cfg, 'sproutHairs', text = \"Subdivide U\")\n\n\nclass HAIRNET_PT_view_panel(bpy.types.Panel):\n    bl_label = \"HairNet\"\n    bl_idname = \"HAIRNET_PT_view_panel\"\n    bl_space_type = \"VIEW_3D\"\n    bl_region_type = \"UI\"\n    bl_category = \"Hair\"\n    bl_context = \"objectmode\"\n    bl_options = {\"DEFAULT_CLOSED\"}\n    \n    \n    def draw(self, context):\n        object = context.active_object\n        if object is not None:\n            self.drawButtons(self.layout)\n            self.drawDetails(self.layout, context)\n    \n    def drawButtons(self, layout):\n        col = layout.box().column(align = True)\n        \n        row = col.row(align = True)\n        row.label(text=\"Make Hair\")\n        \n        row = col.row()\n        row.label(text =\"Add Hair From:\")\n        \n        row = col.row(align = True)\n        for kind in mesh_kinds:\n            row = col.row(align = True)\n            row.operator(\"hairnet.operator\", text=kind[1]).meshKind=kind[0]\n        \n\n    def drawDetails(self, layout, context):\n        self.headObj = context.object\n\n\n        #Get a list of hair objects\n        self.hairObjList = context.selected_objects\n        if self.headObj in self.hairObjList:\n            self.hairObjList.remove(self.headObj)\n\n        layout = self.layout\n\n        row = layout.row()\n        #row.label(text = \"Objects Start here\")\n\n        '''Is this a hair object?'''\n\n        row = layout.row()\n        try:\n            row.prop(self.headObj.hn_cfg, 'isEmitter', text = \"Emit Hair on Self\")\n        except:\n            pass\n\n        #Draw this if this is a head object\n        if not self.headObj.hn_cfg.isEmitter:\n            box = layout.box()\n            row = box.row()\n            row.label(text = \"Hair Object:\")\n            row.label(text = \"Use Settings:\")\n            for thisHairObject in self.hairObjList:\n                config=thisHairObject.hn_cfg\n                row = box.row()\n                row.prop_search(config, 'masterHairSystem',  bpy.data, \"particles\", text = thisHairObject.name)\n                row = box.row()\n                row.label(text = \"Add Guides:\")\n                row.prop(config, 'sproutHairs', text = \"SubD\")\n#                 row.prop(thisHairObject, 'hnSubdivideHairSections', text = \"Subdivide V\")\n\n        #Draw this if it's a self-emitter object\n        else:\n            box = layout.box()\n            try:\n                row = box.row()\n                row.label(text = \"Use Settings\")\n                \n                row = box.row()\n                row.prop_search(self.headObj.hn_cfg, 'masterHairSystem',  bpy.data, \"particles\", text = self.headObj.name)\n\n            except:\n                pass\n            row = box.row()\n            row.label(text = \"Guide Subdivisions:\")\n            row.prop(self.headObj.hn_cfg, 'sproutHairs', text = \"SubD\")\n\n\n\nclasses = (\n    HAIRNET_OT_operator, \n    HAIRNET_PT_panel, \n    HAIRNET_PT_view_panel,\n    HairNetConfig,\n)\n\ndef register():\n    for cls in classes:\n        bpy.utils.register_class(cls)\n    \n    bpy.types.Object.hn_cfg=PointerProperty(type=HairNetConfig)\n\ndef unregister():\n    for cls in reversed(classes):\n        bpy.utils.unregister_class(cls)\n\nif __name__ == '__main__':\n    register()"
  },
  {
    "path": "hairNet/import_properties.py",
    "content": "import bpy\nfrom bpy.props import (StringProperty,\n                       BoolProperty,\n                       IntProperty,\n                       FloatProperty,\n                       FloatVectorProperty,\n                       EnumProperty,\n                       PointerProperty,\n                       CollectionProperty,\n                       )\n\nfrom bpy.types import PropertyGroup"
  }
]