## TODO: In the "get outfits for this model's UI" and related logic, lower() the model name and the outfit data being compared to.
## TODO: Completely revamp the outfit data system, so that DOA Kasumi and ME Kasumi don't have the same filenames and overwrite each other for same-name outfits.

# Outfit Loader v3.5
#
# by LordAardvark

import math
import sfmUtils
import sfmClipEditor
import vs
import string
import random
import json
import os
import time
import struct
import glob
import re

from PySide import QtCore
from PySide import QtGui
from collections import deque

# Globals
if 'OL2_Outfits' not in globals():
    OL2_Outfits = {}
    OL2_TagStates = {}
    OL2_TaggedOutfits = {}
    OL2_LoadedAllData = False
    OL2_ModelsLoaded = []
    
    OL_BindFlexes = True
    OL_UseLock = True
    OL_ScaleOutfit = True    

OL2_TransposableBindTypes = ["outfit","hair"]

if "OL2_TranspositionData" not in globals():
    OL2_TranspositionData = {}
    
CurAnimSet = None

OL2_Epsilon = 0.001
    
OL2_SavePath = "LordAardvarkScriptData/OL2/"
OFL_OutfitsFolderPath = "LordAardvarkScriptData/" # For conversion purposes

qhm = 268435455 # Quick-Hash Maximum. Used for modular arithmetic.

Regex_BoneNameExtraction = re.compile('.*?\((.*?)\)$')

# Classes
class OutfitFlexStore:
    def __init__(self, flexName, flexLValue, flexRValue):
        self.flexName = flexName
        self.flexRValue = flexRValue
        self.flexLValue = flexLValue
        
    def qhash(self):
        return (qhash(self.flexName) * (self.flexRValue + 1) * (self.flexLValue + 1)) % qhm
        
    def __eq__(self,other):
        return (type(self) == type(other)) and (self.flexName == other.flexName) and (self.flexRValue == other.flexRValue) and (self.flexLValue == other.flexLValue)
        
    def __ne__(self,other):
        return not self.__eq__(other)
        
class OutfitComponent:
    def __init__(self, mdlName):
        self.modelName = mdlName.lower()
        self.skinInt = 0
        self.bodyInt = 0
        self.flexes = []
        
    def qhash(self):
        h = qhash(self.modelName) * (self.skinInt + 3) * (self.bodyInt + 3)
        
        for o in self.flexes:
            h = h * o.qhash()
            
        return h % qhm
        
    def __eq__(self,other):
        return (type(self) == type(other)) and (self.modelName.strip() == other.modelName.strip()) and (self.skinInt == other.skinInt) and (self.bodyInt == other.bodyInt)
        
    def __ne__(self,other):
        return not self.__eq__(other)        
        
class OutfitBase:
    def __init__(self, outfitName, mdlName):
        self.outfitName = outfitName
        self.modelName = mdlName.lower()
        self.skinInt = 0
        self.bodyInt = 0
        self.flexes = []
        self.outfitComponents = []
        self.hdFile = None
        self.tagNames = []
        self.stateName = "" # An outfit can have multiple tags it associates with, but it can only have a single state it assigns to.
        self.lastStoredHash = -1
        
    def qhash(self):
        h = qhash(self.modelName) * qhash(self.outfitName) * (self.skinInt + 3) * (self.bodyInt + 3)
        
        for o in self.flexes:
            h = h * o.qhash()
        
        for o in self.outfitComponents:
            h = h * o.qhash()
            
        h = h * qhash(self.hdFile)
        
        for o in self.tagNames:
            h = h * qhash(o)
            
        h = h * qhash(self.stateName)
        
        return h % qhm
        
    def __eq__(self,other):
        e = (type(self) == type(other)) and (self.outfitName.strip() == other.outfitName.strip()) and (self.modelName.strip() == other.modelName.strip()) and (self.skinInt == other.skinInt) and (self.bodyInt == other.bodyInt) and (self.tagNames == other.tagNames) and (self.stateName.strip() == other.stateName.strip())
        
        if e:
            for c in self.outfitComponents:
                if c not in other.outfitComponents:
                    return False
                    
        return e
        
    def __ne__(self,other):
        return not self.__eq__(other)        
        
class State:
    def __init__(self, name, bodyInt, skinInt):
        self.stateName = name
        self.bodyInt = bodyInt
        self.skinInt = skinInt
        self.flexes = []
        self.allFlexes = False

    def qhash(self):
        h = qhash(self.stateName) * (self.bodyInt + 3) * (self.skinInt + 3)
        
        for o in self.flexes:
            h = h * o.qhash()
            
        if self.allFlexes:
            h = h * 2
            
        return h % qhm
        
    def __eq__(self,other):
        return (type(self) == type(other)) and (self.stateName == other.stateName) and (self.bodyInt == other.bodyInt) and (self.skinInt == other.skinInt) and (self.flexes == other.flexes) and (self.allFlexes == other.allFlexes)
        
    def __ne__(self,other):
        return not self.__eq__(other)        
        
class TagState:
    def __init__(self, mdlName):
        global OL2_TagStates
        
        self.modelName = mdlName.lower()
        self.tags = [] # just simple strings
        self.states = [] # State objects
        self.lastStoredHash = -1
        
    def qhash(self):
        h = qhash(self.modelName)
        
        for o in self.tags:
            h = h * qhash(o)
            
        for o in self.states:
            h = h * o.qhash()
            
        return h % qhm
        
    def __eq__(self,other):
        return (type(self) == type(other)) and (self.modelName == other.modelName) and (self.tags == other.tags) and (self.states == other.states)
        
    def __ne__(self,other):
        return not self.__eq__(other)        
        
# Methods
def GetCurAnimset():
    global CurAnimSet
    
    if CurAnimSet is None:
        CurAnimSet = sfm.GetCurrentAnimationSet()

    return CurAnimSet
    
def BindAnimsets(parentAnimset, childAnimset, bindType):
    parentAnimset.AddControl(childAnimset)
    
    childAnimset.AddAttributeAsString("bindtype")
    childAnimset.SetValue("bindtype",bindType)
    
def IsValidTransposableControl(control):
    return control.HasAttribute("bindtype") and str(control.GetAttribute("bindtype")) in OL2_TransposableBindTypes and control.HasAttribute("gameModel") and control.GetAttribute("gameModel").GetValue() is not None
    
def GetOutfitTransposeableAnimsets(shot=None):
    if shot == None:
        shot = sfm.GetCurrentShot()
        
    animsets = []
    
    for animset in shot.animationSets:
        for control in animset.controls:
            if IsValidTransposableControl(control):
                animsets.append(animset)
                break
                
    return animsets
    
def CopyOutfitDataForTransposition(lstAnimsets):
    global OL2_TranspositionData
    
    OL2_TranspositionData = {}
    
    for animset in lstAnimsets:
        outfits = []
        
        for control in animset.controls:
            if IsValidTransposableControl(control):
                outfits.append(control)
                
        outfitData = []
        
        for outfit in outfits:
            outfitName = "Unknown"
            
            if outfit.HasAttribute("outfitName"):
                outfitName = outfit.GetAttribute("outfitName")
                
            transposeData = [str(outfit.gameModel.modelName), str(outfitName), int(str(outfit.gameModel.body)), int(str(outfit.gameModel.skin))]
            
            transposeMaterials = []
            
            if outfit.gameModel.HasAttribute("materials"):
                numMaterials = outfit.gameModel.GetAttribute("materials").Count()
                
                for i in range(0,numMaterials):
                    origMat = outfit.gameModel.materials[i]
                    
                    newMat = vs.CreateElement("DmeMaterial", str(origMat.mtlName), 0)
                    
                    origMat.CopyAttributesTo(newMat)
                    
                    transposeMaterials.append(newMat)
                    
            transposeData.append(transposeMaterials)
            
            outfitData.append(transposeData)
            
        OL2_TranspositionData[str(animset.name)] = outfitData
        
def TransposeStoredOutfits(shot=None):
    if shot == None:
        shot = sfm.GetCurrentShot()
        
    animsetsByName = {}
    
    for animset in shot.animationSets:
        animsetsByName[str(animset.name)] = animset
        
    for animsetName in OL2_TranspositionData:
        if animsetName not in animsetsByName:
            continue
            
        animset = animsetsByName[animsetName]
        outfitData = OL2_TranspositionData[animsetName]
        
        for outfit in outfitData:
            mdlName, outfitName, body, skin, matsList = outfit
            
            outfitAnimsets = EquipOutfitFromModelName(mdlName, outfitName, animset)
            
            if len(outfitAnimsets) < 1:
                continue
                
            outfitAnimset = outfitAnimsets[0]
            
            outfitAnimset.gameModel.body = body
            outfitAnimset.gameModel.skin = skin
            
            if len(matsList) > 0:
               if not hasattr(outfitAnimset.gameModel,"materials"):
                    outfitAnimset.gameModel.AddAttribute('materials', vs.AT_ELEMENT_ARRAY)
                    
            for origMat in matsList:
                newMat = vs.CreateElement("DmeMaterial", str(origMat.mtlName), 0)
                
                origMat.CopyAttributesTo(newMat)
                
                outfitAnimset.gameModel.materials.AddToTail(newMat)
                
def RenameFlexes(animset):
    for ctrl in animset.controls:
        if GetType(ctrl) == "CDmElement":
            ctrlName = str(ctrl.name).replace("-","_")
            ctrl.name = ctrlName
            
def CreateFlexSlave(masterAnimset, slaveAnimset):
    operatorMap = {}
    slaveMap = {}
    
    slaveModel = getattr(slaveAnimset,"gameModel",None)
    masterModel = getattr(masterAnimset,"gameModel",None)
    
    if slaveModel is None or masterModel is None:
        return
        
    # Create a quick map of names to controls, so we can easily set these up
    for ctrl in slaveModel.globalFlexControllers:
        #print ("SLAVE : \"" + str(ctrl.name).replace("-","_") + "\".")
        slaveMap[str(ctrl.name).replace("-","_")] = ctrl
        
    for operator in masterAnimset.operators:
        #print ("MASTER : \"" + str(operator.name).replace("-","_") + "\".")
        operatorMap[str(operator.name).replace("-","_")] = operator
    
    # Create an operator for every flex on the master, if one does not already exist
    for ctrl in masterModel.globalFlexControllers:
        ctrlName = str(ctrl.name).replace("-","_")
        opName = "flexDriver_" + ctrlName
        
        #print ("REQUESTED OPERATOR : \"" + opName + "\"")
        
        if opName not in operatorMap:
            operator = vs.CreateElement("DmeConnectionOperator", opName, masterAnimset.GetFileId())
            operator.SetInput(ctrl,"flexWeight")
            
            masterAnimset.AddOperator(operator)
            
            operatorMap[opName] = operator
        
        # And add an output to the slave, if the slave has one
        if ctrlName not in slaveMap:
            #print "CTRL \"" + ctrlName + " NOT ON SLAVE"
            continue
            
        operator = operatorMap[opName]
        
        operator.AddOutput(slaveMap[ctrlName],"flexWeight")
        
def ZeroCtrl(tgtCtrl):
    logs = []
    channels = [tgtCtrl.positionChannel, tgtCtrl.orientationChannel]
    
    for channel in channels:
        layers = channel.log.layers[0]
        
        logs.append(layers.times)
        logs.append(layers.values)
        
    for log in logs:
        log.SetCount(0)
        log.SetCount(1)
      
def LockAndZero(tgtCtrl, parentCtrl):
    ctrlDag = tgtCtrl.GetDag()
    parentDag = parentCtrl.GetDag()
    
    ctrlDag.EnableOverrideParent(True)
    ctrlDag.SetOverrideParent(parentDag, True, True)
        
    ZeroCtrl(tgtCtrl)
    
def DmAttributeToVector(attr):
    strAttr = str(attr)
    attrSplit = strAttr.split()
    
    return vs.Vector(float(attrSplit[0]), float(attrSplit[1]), float(attrSplit[2]))
    
    
def GetFarthestBone(animset):
    curDist = 0
    curBone = None
    
    if not hasattr(animset,"gameModel"):
        return None
        
    origVector = vs.Vector(0,0,0)
    
    for bone in animset.gameModel.bones:
        bonePos = DmAttributeToVector(bone.position)
        boneDist = origVector.DistTo(bonePos)
        
        if boneDist > curDist:
            curDist = boneDist
            curBone = bone
            
    return curBone
    
def ExtractBoneName(sName):
    extracted = Regex_BoneNameExtraction.match(sName)
    
    if extracted is None:
        extracted = sName
    else:
        extracted = extracted.group(1)
        
    return extracted
    
def GetOutfitScale(baseAnimset, outfitAnimset):
    if not hasattr(outfitAnimset,"gameModel"):
        return 0.0
        
    testingBaseBone = GetFarthestBone(baseAnimset)
    testingOutfitBone = None
    testingBaseBoneName = ExtractBoneName(str(testingBaseBone.name))
    
    for bone in outfitAnimset.gameModel.bones:
        boneName = ExtractBoneName(str(bone.name))
        
        if boneName == testingBaseBoneName:
            testingOutfitBone = bone
            break
            
    if testingOutfitBone is None:
        return 0.0
    
    origVector = vs.Vector(0,0,0)
    
    testingBaseDistance = origVector.DistTo(DmAttributeToVector(testingBaseBone.position))
    testingOutfitDistance = origVector.DistTo(DmAttributeToVector(testingOutfitBone.position))
    
    return testingBaseDistance / testingOutfitDistance
    
def ApplyScaleToAnimset(animset, scale, compareAnimset=None):
    if not hasattr(animset,"controls"):
        return
        
    if abs(scale - 1.0) < OL2_Epsilon:
        return
        
    compareAnimsetBones = None
    if compareAnimset is not None and hasattr(compareAnimset,"controls"):
        compareAnimsetBones = {}
        
        for ctrl in compareAnimset.controls:
            compareAnimsetBones[str(ctrl.name)] = ctrl
            
    for ctrl in animset.controls:
        if not hasattr(ctrl,"positionChannel"):
            continue
            
        if compareAnimsetBones is not None:
            if str(ctrl.name) not in compareAnimsetBones:
                continue
                
        ctrl.positionChannel.toElement.AddAttributeAsFloat("scale")
        ctrl.positionChannel.toElement.SetValue("scale", scale)
    
# Generate a quick hash of a string. Only use for rudimentary data checking.
def qhash(str):
    if str == None:
        return 3
        
    l = list(str.strip().lower())
    h = 1
    
    for c in l:
        h = (h * ord(c)) % qhm
        
    return h
    
# Get the type of an object
def GetType( var ):
    return type(var).__name__
    
# https://stackoverflow.com/a/6330109
def SCast(val, to_type, default=None):
    try:
        return to_type(val)
    except (ValueError, TypeError):
        return default

# Determine if an animation set is visible.
def IsAnimSetVisible(animSet):
    shot = sfm.GetCurrentShot()
    
    animSetName = str(animSet.name)
    
    for child in shot.scene.children:        
        if str(child.name) == animSetName: # Compare names, since one is a DmeAnimationSet, and the other a DmeDag
            return str(child.visible) == "1"
            
    return False
    
# Make all of the relevant directories in a filepath; Boo not using Python 3.2, means I have to do it this ugly way.
def mkdir_p(path): # http://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
    try:
        os.makedirs(path)
    except OSError as exc:  # Python >2.5
        if os.path.isdir(path):
            pass
        else:
            raise
            
# Given a filepath, just return the actual filename.
def GetRawFileName(fName):
    return os.path.splitext(os.path.basename(fName))[0]
    
# A more convenient file-write
def fwrite(f, sTxt, *args):
    f.write((str(sTxt) + "\n").format(*args))
    
# Given a DmeDag with children, return the first DmeDag with the provided name.
def GrabChild(parent,childName):
    return parent.GetChild(parent.FindChild(childName))
    
def CountInstances(mdlName):
    shot = sfm.GetCurrentShot()
    cnt = 0
    
    animsetNameRoot = GetRawFileName(mdlName).lower()
    
    for child in shot.animationSets:
        if hasattr(child,"gameModel"):
            childName = str(child.name).lower()
            
            if animsetNameRoot in str(childName):
                strNumber = childName.replace(animsetNameRoot,"")
                castNumber = SCast(strNumber, int, -1)
                
                if castNumber > cnt:
                    cnt = castNumber
                
    return cnt
    
# Given an OutfitBase object and a tag, apply that tag and adjust the relevant floating data structure
def AddTagToOutfit(outfitBaseObj, strTag):
    global OL2_TaggedOutfits
        
    if strTag not in outfitBaseObj.tagNames:
        outfitBaseObj.tagNames.append(strTag)
    
    if strTag not in OL2_TaggedOutfits:
        OL2_TaggedOutfits[strTag] = []
        
    if outfitBaseObj not in OL2_TaggedOutfits[strTag]:
        OL2_TaggedOutfits[strTag].append(outfitBaseObj)
    
# Given a TagState object and a string statename, will return True if that statename exists in any of the stored states.
def TagStateHasState(tagStateObj, stateName):
    for stateObj in tagStateObj.states:
        if stateObj.stateName == stateName:
            return True
            
    return False
    
# Given an OutfitBase object, save it to file.
def SaveOutfit(outfitBaseObj):
    global OL2_SavePath

    saveName = GetRawFileName(outfitBaseObj.modelName) + "_-_" + outfitBaseObj.outfitName
    
    fPath = OL2_SavePath + saveName + ".OL2"
    
    outfitBaseObj.hdFile = fPath
    
    # Before we even begin, see if we need to save this object
    h = outfitBaseObj.qhash()
    
    if h == outfitBaseObj.lastStoredHash: # If our hash is the same, we haven't changed. So don't save.
        return
        
    outfitBaseObj.lastStoredHash = h # Otherwise, we do save. Store this new hash.
    
    mkdir_p(OL2_SavePath) # Make sure the path exists before trying to write to it!
    
    # And now we actually write
    with open(fPath,'w') as f:
        # Start by writing out the OutfitBase object, ending with the number of components in the outfit, each component, the number of flexes stored, and the flexes.
        fwrite(f,outfitBaseObj.outfitName)
        fwrite(f,outfitBaseObj.modelName)
        fwrite(f,outfitBaseObj.skinInt)
        fwrite(f,outfitBaseObj.bodyInt)
        
        # Components
        fwrite(f,len(outfitBaseObj.outfitComponents))
        
        for comp in outfitBaseObj.outfitComponents:
            fwrite(f,comp.modelName)
            fwrite(f,comp.skinInt)
            fwrite(f,comp.bodyInt)
            
            # Each component can also have flexes...
            fwrite(f, len(comp.flexes))
            
            for flex in comp.flexes:
                fwrite(f,flex.flexName)
                fwrite(f,flex.flexLValue)
                fwrite(f,flex.flexRValue)
            
        # Flexes
        fwrite(f,len(outfitBaseObj.flexes))
        
        for flex in outfitBaseObj.flexes:
            fwrite(f,flex.flexName)
            fwrite(f,flex.flexLValue)
            fwrite(f,flex.flexRValue)
            
        # Tags and state
        fwrite(f,len(outfitBaseObj.tagNames))
        for tag in outfitBaseObj.tagNames:
            fwrite(f,tag)
            
        fwrite(f,outfitBaseObj.stateName)
            
# Given a TagState object, write it to file.
def SaveTagState(tagStateObj):
    global OL2_SavePath
    
    # Before we even begin, see if we need to save this object
    h = tagStateObj.qhash()
    
    if h == tagStateObj.lastStoredHash: # If our hash is the same, we haven't changed. So don't save.
        return
        
    # print ("Saving tag-state \"" + (str(tagStateObj)) + "\"")
     
    tagStateObj.lastStoredHash = h # Otherwise, we do save. Store this new hash.
    
    saveName = GetRawFileName(tagStateObj.modelName) + "_TagState"
    
    fPath = OL2_SavePath + saveName + ".OL2"
    
    mkdir_p(OL2_SavePath) # Make sure the path exists before trying to write to it!
    
    # And now we actually write
    with open(fPath,'w') as f:
        # Start by writing the name of the model this is for, in full form.
        fwrite(f,tagStateObj.modelName)
        
        # Then, write the tags
        fwrite(f,len(tagStateObj.tags))
        
        for tag in tagStateObj.tags:
            fwrite(f,tag)
            
        # Next, write the states
        fwrite(f,len(tagStateObj.states))
        for stateObj in tagStateObj.states:
            # Name, bodygroup and skin
            fwrite(f,stateObj.stateName)
            fwrite(f,stateObj.bodyInt)
            fwrite(f,stateObj.skinInt)
            fwrite(f,1 if stateObj.allFlexes else 0)
            
            # Flexes
            fwrite(f, len(stateObj.flexes))
            
            for flex in stateObj.flexes:
                fwrite(f,flex.flexName)
                fwrite(f,flex.flexLValue)
                fwrite(f,flex.flexRValue)   
     
            
# Given a string filepath to an OL2 outfit, load it into memoy
def LoadOutfit(fName):
    global OL2_Outfits
    global OL2_SavePath
    
    # If it ain't an OL2, don't read it.
    if not fName.endswith(".OL2"):
        return None
        
    # Read the file exactly as we wrote it
    with open(fName,'r') as f:
        # Get the outfit and model names, build the OutfitBase object, and fill it with skin & body ints
        oName = f.readline().strip()
        mdlName = f.readline().strip()
        
        outfitBaseObj = OutfitBase(oName, mdlName)
        outfitBaseObj.skinInt = int(f.readline())
        outfitBaseObj.bodyInt = int(f.readline())
        outfitBaseObj.hdFile = fName
        
        # Then loop through however many components it says it is, and load each component
        numComponents = int(f.readline())
        
        for i in range(0,numComponents):
            compMdlName = f.readline().strip()
            
            outfitCompObj = OutfitComponent(compMdlName)
            outfitCompObj.skinInt = int(f.readline())
            outfitCompObj.bodyInt = int(f.readline())
            
            # Component flexes 
            numFlexes = int(f.readline())
            for i in range(0,numFlexes):
                flexName = f.readline().strip()
                flexLValue = float(f.readline())
                flexRValue = float(f.readline())
                
                flexObj = OutfitFlexStore(flexName, flexLValue, flexRValue)
                
                outfitCompObj.flexes.append(flexObj)
                
            # Store this component onto the base object    
            outfitBaseObj.outfitComponents.append(outfitCompObj)
            
        # And then loop through the flexes on the base object itself.
        numFlexes = int(f.readline())
        
        for i in range(0,numFlexes):
            flexName = f.readline().strip()
            flexLValue = float(f.readline())
            flexRValue = float(f.readline())
            
            flexObj = OutfitFlexStore(flexName,flexLValue,flexRValue)
            
            outfitBaseObj.flexes.append(flexObj)
           
        # Next up is tags and state, if this file has them.
        numTags = f.readline().strip()

        if (len(numTags) > 0): # This will be an empty line, or len zero, if the file is from v2.0.0, and so doesn't have tags and states
            numTags = int(numTags)
            
            for i in range(0,numTags):
                tagName = f.readline().strip()
                
                # Add this tag via the AddTag method, for manipulating relevant file-structures
                AddTagToOutfit(outfitBaseObj, tagName)
                
            outfitBaseObj.stateName = f.readline().strip()
                
        # Finally, store the outfitBase in the global dictionary
        outfitBaseObj.lastStoredHash = outfitBaseObj.qhash()
        
        if mdlName not in OL2_Outfits:
            OL2_Outfits[mdlName] = {}
            
        OL2_Outfits[mdlName][oName] = outfitBaseObj
        
# Given a string filepath to a TagState OL2 file, load it into memory
def LoadTagState(fName):
    global OL2_TagStates
    global OL2_SavePath
        
    # If it ain't an OL2, don't read it.
    if not fName.endswith(".OL2"):
        return None
        
    # Read the file exactly as we wrote it
    with open(fName,'r') as f:
        # Get the model name, build the TagState object, and fill it with tags and states
        mdlName = f.readline().strip()

        tagStateObj = TagState(mdlName)
        
        # Load tags
        numTags = int(f.readline())
        
        for i in range(0,numTags):
            tagName = f.readline().strip()
            
            if tagName not in tagStateObj.tags:
                tagStateObj.tags.append(tagName)
            
            # And also load this into memory for each tag
            if tagName not in OL2_TagStates:
                OL2_TagStates[tagName] = []
            
            if tagStateObj not in OL2_TagStates[tagName]:
                OL2_TagStates[tagName].append(tagStateObj)
            
        # Load states
        numStates = int(f.readline())
        
        for i in range(0,numStates):
            stateName = f.readline().strip()
            bodyInt = int(f.readline())
            skinInt = int(f.readline())
            iAllFlexes = int(f.readline())
            bAllFlexes = True if iAllFlexes > 0 else False
            
            stateObj = State(stateName, bodyInt, skinInt)
            stateObj.allFlexes = bAllFlexes
            
            if stateObj not in tagStateObj.states:
                tagStateObj.states.append(stateObj)
            
            # Load flexes within this state
            numFlexes = int(f.readline())
            for i in range(0,numFlexes):
                flexName = f.readline().strip()
                flexLValue = float(f.readline())
                flexRValue = float(f.readline())
                
                flexObj = OutfitFlexStore(flexName, flexLValue, flexRValue)
                
                stateObj.flexes.append(flexObj)  
            
        tagStateObj.lastStoredHash = tagStateObj.qhash()
        
# Given a tag name, will all outfits with that tag name to all relevant models
def MapStatedOutfitsWithTag(tagName):
    if tagName not in OL2_TagStates: # Bad state/tag pair. Just ignore it.
        return
        
    tagStateObjs = OL2_TagStates[tagName]
    outfitBases = OL2_TaggedOutfits[tagName]
    
    toAdd = []
    
    # Go through all of the outfits that have this tag
    for outfitBaseObj in outfitBases:
        stateName = outfitBaseObj.stateName
        
        # Determine if there is at least one TagState that has this outfit's state
        for tagStateObj in tagStateObjs:
            if TagStateHasState(tagStateObj,stateName):
                # If so, then assign this outfit to that TagState's tied model
                mdlName = tagStateObj.modelName
                
                toAdd.append( [outfitBaseObj, mdlName, tagName, stateName] )
                
    # And then add them once done iterating, to avoid throwing SFM into another fucking infinite loop. Stupid, stupid, stupid.
    for data in toAdd:
        CreateOutfitFromTagState(data[0], data[1], data[2], data[3])
        
                
# A method that should only be called after all outfits and TagStates have been loaded. When called, will map all outfits with tag-state to all models with any of their tags & states
def MapStatedOutfitsToModels():
    global OL2_TagStates
    global OL2_TaggedOutfits
    global OL2_Outfits
        
    # Go through all of the tags that have been loaded
    for tagName in OL2_TaggedOutfits:
        MapStatedOutfitsWithTag(tagName)
        
# Given a string model-name, will return a list of all TagState objects that correspond to that model
def GetTagStatesForModel(mdlNameBase):
    rList = []
    
    mdlName = mdlNameBase.lower()
    
    for tagName in OL2_TagStates:
        for tagStateObj in OL2_TagStates[tagName]:
            if tagStateObj.modelName == mdlName:
                rList.append(tagStateObj)
            
    return rList
    
# Given an animset, will return a list of all TagState objects that correspond to that animset
def GetTagStatesForAnimset(animset):
    mdlName = str(animset.gameModel.modelName).lower().strip()
    
    return GetTagStatesForModel(mdlName)
    
# Given a list of TagState objects, will return a list of all unique state names in that list
def GetStatesFromTagStates(tagStateObjs):
    rList = []
    
    for tagStateObj in tagStateObjs:
        for stateObj in tagStateObj.states:
            if stateObj.stateName not in rList:
                rList.append(stateObj.stateName)
                
    return rList
    
# Given a list of TagState objects, will return a list of all unique tag names in that list
def GetTagsFromTagStates(tagStateObjs):
    rList = []
    
    for tagStateObj in tagStateObjs:
        for tagName in tagStateObj.tags:
            if tagName not in rList:
                rList.append(tagName)
                
    return rList
    
# Given an animation set, and an optional name for the (potential) new tag, will return the first TagSet targeting that animset's model, OR will create, store, and return a new one.
def GetOperatingTagState(animSet, newTagName="Default"):
    tagStateObjs = GetTagStatesForAnimset(animSet)
    
    if len(tagStateObjs) < 1: # No existing TagState? Create one.
        curTagState = TagState(str(animSet.gameModel.modelName).strip().lower())
    else: # At least one TagState? Use the first.
        curTagState = tagStateObjs[0]
        
    # Ensure this tag is in the TagState
    if newTagName not in OL2_TagStates:
        OL2_TagStates[newTagName] = []
        
    if curTagState not in OL2_TagStates[newTagName]:
        OL2_TagStates[newTagName].append(curTagState)
        
    return curTagState
        
# Create a State object for the given animation set, and store it into a provided TagState object
def AddStateToTagState(tagStateObj, stateName, animSet, bUseBodygroup, bUseSkin, bSaveFlexes, bSaveAllFlexes):
    bodyInt = int(str(animSet.gameModel.body)) if bUseBodygroup else -1
    skinInt = int(str(animSet.gameModel.skin)) if bUseSkin else -1
    
    stateObj = State(stateName, bodyInt, skinInt)
    stateObj.allFlexes = 1 if bSaveAllFlexes else 0
    
    if bSaveFlexes:
        StoreFlexes(animSet, stateObj.flexes, bSaveAllFlexes)

    if stateObj not in tagStateObj.states:
        tagStateObj.states.append(stateObj)
    
# Given a TagState object, and a state name: remove that state from the TagState object
def RemoveStateFromTagState(tagStateObj, stateName):
    toDel = []
    
    for stateObj in tagStateObj.states:
        if stateObj.stateName == stateName:
            toDel.append(stateObj)
            
    for stateObj in toDel:
        tagStateObj.states.remove(stateObj)
        
# Given a TagState object and a string tag-name: remove that tag from the TagState object
def RemoveTagFromTagState(tagStateObj, tagName):
    tagStateObj.tags.remove(tagName)
    
# Given a string model-name, a string tag-name, and a string state-name: return the matching State object from the relevant TagState object, if one exists. Otherwise, return None.
def GetStateForModel(mdlNameBase, tagName, stateName):
    mdlName = mdlNameBase.lower()
    
    for sTagName in OL2_TagStates:
        if sTagName == tagName:
            tagStateObjs = OL2_TagStates[sTagName]
            
            for tagStateObj in tagStateObjs:
                if tagStateObj.modelName == mdlName:
                    for stateObj in tagStateObj.states:
                        if stateObj.stateName == stateName:
                            return stateObj
                    
    return None
    
# Given an animation set, will load all of the outfits that use it as a base model.
def LoadOutfitsForAnimSet(animSet):
    global OL2_Outfits
    global OL2_SavePath
    
    if not os.path.exists(OL2_SavePath):
        mkdir_p(OL2_SavePath)
        
    mdlName = str(animSet.gameModel.modelName).lower().strip()
    
    # Load the stored OFLs into memory
    for fName in os.listdir(OL2_SavePath):
        fPath = OL2_SavePath + fName
        
        if os.path.isfile(fPath) and fName.endswith(".OL2"):            
            f = open(fPath)
            
            f.readline() # Burn through the outfit name.
            oMdlName = f.readline().strip()
            
            f.close()
            
            if oMdlName == mdlName:
                LoadOutfit(fPath)
                
def LoadDataForModelName(fullMdlName):
    mdlName = GetRawFileName(fullMdlName)
    
    # Fetch the outfit loader data
    olData = glob.glob(os.path.join(OL2_SavePath, mdlName + "_TagState.OL2"))

    for fName in olData:
        LoadTagState(fName)
        
    olData = glob.glob(os.path.join(OL2_SavePath, mdlName + "_-_*.OL2"))

    for fName in olData:
        LoadOutfit(fName)
        
def LoadDataInitialize():
    global OL2_LoadedAllData
    global OL2_ModelsLoaded
    
    if OL2_LoadedAllData:
        return
        
    animSet = GetCurAnimset()
    
    if not hasattr(animSet,"gameModel"):
        return
        
    mdlName = str(animSet.gameModel.modelName).lower()
    
    if mdlName in OL2_ModelsLoaded:
        return
        
    LoadDataForModelName(mdlName)
    
    OL2_ModelsLoaded.append(mdlName)
    
# Reload all data into memory, clearing everything first
def ReloadEverything():
    global OL2_Outfits
    global OL2_TagStates
    global OL2_TaggedOutfits
    
    OL2_Outfits = {}
    OL2_TagStates = {}
    OL2_TaggedOutfits = {}
    
    LoadEverything()
    
# Load all data into memory
def LoadEverything():
    global OL2_SavePath
    global OL2_LoadedAllData
    global OL2_ModelsLoaded
   
    OL2_ModelsLoaded = []
    OL2_LoadedAllData = True
    
    if not os.path.exists(OL2_SavePath):
        mkdir_p(OL2_SavePath)

    # Load the stored OFLs into memory
    for fName in os.listdir(OL2_SavePath):
        fPath = OL2_SavePath + fName
        
        if os.path.isfile(fPath) and fName.endswith(".OL2"):

            if "_TagState" in fName: # It's a TagState
                LoadTagState(fPath)
            else: # It's an Outfit
                LoadOutfit(fPath)
                
    # And then map all outfits
    MapStatedOutfitsToModels()

# Load all data into memory, if memory is currently empty
def LoadEverythingInitialize():
    if len(OL2_Outfits) < 1:
        LoadEverything()
        
# Given a Flex channel, and a value, assign that flex value.
def SetFlexValue(chnl, val):
    fLog = chnl.log.layers[0]
    
    if len(fLog.times) > 0: # If this has any keyframes, then we need to zero them out and set the first's value
        # Burn through everything until there's only one value left.
        for i in range(0,len(fLog.times) - 1):
            del fLog.times[0]
            del fLog.values[0]
            
        # And then set that last one
        fLog.times[0] = 0
        fLog.values[0] = val
    else: # Otherwise, we need to make a new one and a new value
        fLog.times.append(0.0)
        fLog.values.append(val)
    
# Given a Flex channel, get the last-keyframed value of that flex, or 0 if it's not keyframed.
def GetFlexValue(chnl):
    fLog = chnl.log.layers[0]
    
    if len(fLog.times) < 1:
        return 0.0
        
    return fLog.values[len(fLog.values) - 1] # Return the last keyframed animation
    
# Given an AnimSet, return a dictionary mapping flex names to control indices
def MapFlexes(animSet):
    flexNameMap = {}
    
    for i in range(0,len(animSet.controls)):
        flex = animSet.controls[i]
        
        if GetType(flex) == "CDmElement":
            flexNameMap[str(flex.name).lower()] = i
            
    return flexNameMap
    
# Giving an animation set, and a list of OutfitFlexStore objects, apply all of the FlexStores to the animation set.
def ApplyFlexes(animSet, lstFlexStore):
    # Create a map of flex names to indices
    flexNameMap = MapFlexes(animSet)
            
    # And then use that map to apply these values
    for flexStore in lstFlexStore:
        if flexStore.flexName not in flexNameMap:
            continue
            
        flex = animSet.controls[flexNameMap[flexStore.flexName]]
        
        if hasattr(flex,"channel"):
            SetFlexValue(flex.channel,flexStore.flexLValue)
           
        if hasattr(flex,"rightvaluechannel"):
            SetFlexValue(flex.leftvaluechannel,flexStore.flexLValue)
            SetFlexValue(flex.rightvaluechannel,flexStore.flexRValue)
            
# Given an animation set, a list of OutfitFlexStore objects, and a Bool if it should save all flexes or not, will store all of the relevant flexes into the provided list
def StoreFlexes(animSet, lstFlexStore, bSaveAllFlexes):
    flexNameMap = MapFlexes(animSet)
    
    for flexName in flexNameMap:
        flex = animSet.controls[flexNameMap[flexName]]
        
        lValue = 0.0
        rValue = 0.0
        
        if hasattr(flex,"channel"):
            lValue = GetFlexValue(flex.channel)
            rValue = lValue
           
        if hasattr(flex,"rightvaluechannel"):
            lValue = GetFlexValue(flex.leftvaluechannel)
            rValue = GetFlexValue(flex.rightvaluechannel)

        if ((lValue + rValue) > 0.0) or (bSaveAllFlexes): # Only save if it's greater than zero, OR we just save them all
            flexStore = OutfitFlexStore(flexName, lValue, rValue)
            
            lstFlexStore.append(flexStore)
            
def AddOrCreateSceneGroup(grpName):
    shot = sfm.GetCurrentShot()
    
    for sceneElement in shot.scene.children:
        sceneElementName = str(sceneElement.name)
        
        if sceneElementName == grpName:
            return sceneElement
            
    grp = vs.CreateElement("DmeDag", grpName, shot.GetFileId())
    
    shot.scene.AddChild(grp)
    
    return grp
    
# Given an OutfitBase object, and an AnimSet, actually equip the outfit
def EquipOutfit(outfitBaseObj, animSet):
    global OL_BindFlexes
    global OL_UseLock
    global OL_ScaleOutfit
    
    animsetsSpawned = []
    
    # Check to make sure this outfit actually belongs to this model
    mdlName = str(animSet.gameModel.modelName).lower()
    
    if mdlName != outfitBaseObj.modelName:
        return None
        
    # Then set the skin and body ints, if they're greater than -1.
    if outfitBaseObj.bodyInt >= 0:
        animSet.gameModel.body = outfitBaseObj.bodyInt
        
    if outfitBaseObj.skinInt >= 0:
        animSet.gameModel.skin = outfitBaseObj.skinInt
        
    # Apply all the stored flexes. This will require nuking all of the existing information first, though, so be careful with it.
    if len(outfitBaseObj.flexes) > 0: # Only do this if we have to. Most times, hopefully, we won't have to.
        ApplyFlexes(animSet, outfitBaseObj.flexes)
        
    # Go through all of the master's bones, and just store their names
    animSetBones = []
     
    for ctrl in animSet.controls:
        if ctrl.GetTypeString() == "DmeTransformControl": # We only want bones
            animSetBones.append(str(ctrl.name))
            
    # And then spawn, position, and parent all of the outfit components
    i = 0
    
    animSetRoot = animSet.FindControl( "rootTransform" )
    animSetName = str(animSet.name)
            
    pos = animSetRoot.GetPosition()
    rot = animSetRoot.GetOrientation().AsRadianEuler() # Because fuck Quaternions
    
    # Create a group to store them all in
    shot = sfm.GetCurrentShot()
 
    grpName = animSetName + " :: " + outfitBaseObj.outfitName
    grp = AddOrCreateSceneGroup(grpName)
    
    for comp in outfitBaseObj.outfitComponents:
        i = i + 1 # Curse Python for not having an incrementor
        
        outfitMdl = comp.modelName
        
        #outfitAnimSetName = animSetName + "_" + outfitBaseObj.outfitName + str(i)
        outfitAnimSetName = GetRawFileName(outfitMdl) + str(CountInstances(outfitMdl) + 1)
        
        outfitAnimSet = sfmUtils.CreateModelAnimationSet(str(outfitAnimSetName), str(outfitMdl))
        outfitAnimSet.visible = True
        
        # Rename the flexes as applicable
        RenameFlexes(outfitAnimSet)
        
        if comp.bodyInt >= 0:
            outfitAnimSet.gameModel.body = comp.bodyInt
            
        if comp.skinInt >= 0:
            outfitAnimSet.gameModel.skin = comp.skinInt
            
        if len(comp.flexes) > 0: # And set the flexes on the component.
            ApplyFlexes(outfitAnimSet, comp.flexes)
            
        # Position this outfit's rootTransform atop the master's
        sfm.SetOperationMode( "Record" ) # Change the operation mode so changes can be made

        sfm.PushSelection() # Push the current selection to preserve it

        sfm.ClearSelection()
        sfm.Select(outfitAnimSetName + ":rootTransform")

        sfm.Move(pos[0], pos[1], pos[2])
        sfm.Rotate(math.degrees(rot.x), math.degrees(rot.y), math.degrees(rot.z))

        sfm.PopSelection() # Pop the current selection to restore the previous selection
        
        sfm.SetOperationMode( "Pass" ) # Restore the operation mode
        
        # Bind flexes
        if OL_BindFlexes:
            CreateFlexSlave(animSet, outfitAnimSet)
        
        #bUseLock = True
        
        # Parent all of the bones that can be parented.
        for bone in animSetBones:
            outfitBone = outfitAnimSet.FindControl(bone)
            
            if outfitBone != None:
                parentName = animSetName + ":" + bone
                childName = outfitAnimSetName + ":" + bone
                
                if OL_UseLock:
                    parentBone = animSet.FindControl(bone)
                    
                    LockAndZero(outfitBone, parentBone)
                else:
                    sfm.ParentConstraint(parentName, childName)
                    
                
        # Apply the relevant scale to the outfit.
        if OL_ScaleOutfit:
            outfitScale = GetOutfitScale(animSet, outfitAnimSet)
            ApplyScaleToAnimset(outfitAnimSet, outfitScale, compareAnimset=animSet)
        
        # Hide the selectability of the outfit
        outfitAnimSet.GetRootControlGroup().SetSelectable(False)
        
        # Bind the animsets for other scripts, and add the outfit name to the outfit object.
        BindAnimsets(animSet, outfitAnimSet, "outfit")
        
        outfitAnimSet.AddAttributeAsString("outfitName")
        outfitAnimSet.SetValue("outfitName",outfitBaseObj.outfitName)
        
        # And then remove the animset from the base scene, instead adding it to the new group.
        outfitDag = GrabChild(shot.scene, outfitAnimSetName)
        
        shot.scene.RemoveChild(outfitDag)
        grp.AddChild(outfitDag)
        
        animsetsSpawned.append(outfitAnimSet)
        
    return animsetsSpawned
    
# Given a model name, an outfit name, and an AnimSet, actually equip the model as an outfit.
def EquipOutfitFromModelName(outfitMdl, outfitName, animSet):
    global OL_BindFlexes
    global OL_UseLock
    global OL_ScaleOutfit
    
    animsetsSpawned = []
    
    # Go through all of the master's bones, and just store their names
    animSetBones = []
     
    for ctrl in animSet.controls:
        if ctrl.GetTypeString() == "DmeTransformControl": # We only want bones
            animSetBones.append(str(ctrl.name))
            
    # And then spawn, position, and parent all of the outfit components
    i = 0
    
    animSetRoot = animSet.FindControl( "rootTransform" )
    animSetName = str(animSet.name)
            
    pos = animSetRoot.GetPosition()
    rot = animSetRoot.GetOrientation().AsRadianEuler() # Because fuck Quaternions
    
    # Create a group to store them all in
    shot = sfm.GetCurrentShot()
 
    grpName = animSetName + " :: " + outfitName
    grp = AddOrCreateSceneGroup(grpName)

    outfitAnimSetName = GetRawFileName(outfitMdl) + str(CountInstances(outfitMdl) + 1)
    
    outfitAnimSet = sfmUtils.CreateModelAnimationSet(str(outfitAnimSetName), str(outfitMdl))
    outfitAnimSet.visible = True
    
    # Rename the flexes as applicable
    RenameFlexes(outfitAnimSet)

    # Position this outfit's rootTransform atop the master's
    sfm.SetOperationMode( "Record" ) # Change the operation mode so changes can be made

    sfm.PushSelection() # Push the current selection to preserve it

    sfm.ClearSelection()
    sfm.Select(outfitAnimSetName + ":rootTransform")

    sfm.Move(pos[0], pos[1], pos[2])
    sfm.Rotate(math.degrees(rot.x), math.degrees(rot.y), math.degrees(rot.z))

    sfm.PopSelection() # Pop the current selection to restore the previous selection
    
    sfm.SetOperationMode( "Pass" ) # Restore the operation mode
    
    # Bind flexes
    if OL_BindFlexes:
        CreateFlexSlave(animSet, outfitAnimSet)
    
    #bUseLock = True
    
    # Parent all of the bones that can be parented.
    for bone in animSetBones:
        outfitBone = outfitAnimSet.FindControl(bone)
        
        if outfitBone != None:
            parentName = animSetName + ":" + bone
            childName = outfitAnimSetName + ":" + bone
            
            if OL_UseLock:
                parentBone = animSet.FindControl(bone)
                
                LockAndZero(outfitBone, parentBone)
            else:
                sfm.ParentConstraint(parentName, childName)
                
            
    # Apply the relevant scale to the outfit.
    if OL_ScaleOutfit:
        outfitScale = GetOutfitScale(animSet, outfitAnimSet)
        ApplyScaleToAnimset(outfitAnimSet, outfitScale, compareAnimset=animSet)
    
    # Hide the selectability of the outfit
    outfitAnimSet.GetRootControlGroup().SetSelectable(False)
    
    # Bind the animsets for other scripts
    BindAnimsets(animSet, outfitAnimSet, "outfit")
    
    outfitAnimSet.AddAttributeAsString("outfitName")
    outfitAnimSet.SetValue("outfitName",outfitName)
        
    # And then remove the animset from the base scene, instead adding it to the new group.
    outfitDag = GrabChild(shot.scene, outfitAnimSetName)
    
    shot.scene.RemoveChild(outfitDag)
    grp.AddChild(outfitDag)
    
    animsetsSpawned.append(outfitAnimSet)
        
    return animsetsSpawned
        
# Given an outfit-base animation set, an outfit name string, a list of outfit-component animation sets, and then 4 bools for: keeping bodygroup on base outfit; keeping skin on base outfit; saving non-zero flexes on base outfit AND outfit components; saving ALL flexes on base outfit AND outfit components: create an outfit.
def CreateOutfit(outfitBaseAnim, outfitName, lstOutfitComponentAnims, bUseBodygroup, bUseSkin, bSaveFlexes, bSaveAllFlexes):
    mdlName = str(outfitBaseAnim.gameModel.modelName).lower()
    
    outfitBaseObj = OutfitBase(outfitName, mdlName)
    outfitBaseObj.bodyInt = int(str(outfitBaseAnim.gameModel.body)) if bUseBodygroup else -1
    outfitBaseObj.skinInt = int(str(outfitBaseAnim.gameModel.skin)) if bUseSkin else -1
    
    for compAnim in lstOutfitComponentAnims:
        compMdlName = str(compAnim.gameModel.modelName).lower()
        
        outfitCompObj = OutfitComponent(compMdlName)
        outfitCompObj.bodyInt = int(str(compAnim.gameModel.body))
        outfitCompObj.skinInt = int(str(compAnim.gameModel.skin))
        
        if bSaveFlexes:
            StoreFlexes(compAnim, outfitCompObj.flexes, bSaveAllFlexes)

        outfitBaseObj.outfitComponents.append(outfitCompObj)
        
    if bSaveFlexes:
        StoreFlexes(outfitBaseAnim, outfitBaseObj.flexes, bSaveAllFlexes)
            
    # Then store this outfit in the dictionary, and save it to file.
    if mdlName not in OL2_Outfits:
        OL2_Outfits[mdlName] = {}
        
    if outfitName in OL2_Outfits[mdlName]:
        if OL2_Outfits[mdlName][outfitName] == outfitBaseObj:
            return
               
    OL2_Outfits[mdlName][outfitName] = outfitBaseObj 

    SaveOutfit(outfitBaseObj)
    
# Given an outfit-base animation set, an outfit name, a list of outfit-component animation sets, a string tag-name, and a string state-name: create the outfit using the state, rather than the actual bodygroup/skingroup/flex set directly.
def CreateOutfitWithState(animSet, outfitName, lstOutfitComponentAnims, tagName, stateName):
    mdlName = str(animSet.gameModel.modelName).lower()
    stateObj = GetStateForModel(mdlName, tagName, stateName)
    
    outfitBaseObj = OutfitBase(outfitName, mdlName)
    outfitBaseObj.bodyInt = stateObj.bodyInt
    outfitBaseObj.skinInt = stateObj.skinInt
    
    bSaveFlexes = len(stateObj.flexes) > 0
    bSaveAllFlexes = stateObj.allFlexes
    
    for compAnim in lstOutfitComponentAnims:
        compMdlName = str(compAnim.gameModel.modelName).lower()
        
        outfitCompObj = OutfitComponent(compMdlName)
        outfitCompObj.bodyInt = int(str(compAnim.gameModel.body))
        outfitCompObj.skinInt = int(str(compAnim.gameModel.skin))
        
        if bSaveFlexes:
            StoreFlexes(compAnim, outfitCompObj.flexes, bSaveAllFlexes)

        outfitBaseObj.outfitComponents.append(outfitCompObj)
        
    outfitBaseObj.flexes = stateObj.flexes
    
    outfitBaseObj.stateName = stateName
    
    AddTagToOutfit(outfitBaseObj, tagName)
    
    # Then store this outfit in the dictionary, and save it to file.
    if mdlName not in OL2_Outfits:
        OL2_Outfits[mdlName] = {}
        
    if outfitName in OL2_Outfits[mdlName]:
        if OL2_Outfits[mdlName][outfitName] == outfitBaseObj:
            return
               
    OL2_Outfits[mdlName][outfitName] = outfitBaseObj 

    SaveOutfit(outfitBaseObj)

    
# Given an OutfitBase object, a new model name, a string tag-name, and a string state-name: create an outfit, mapping the state to the model
def CreateOutfitFromTagState(origOutfitBaseObj, mdlName, tagName, stateName):
    stateObj = GetStateForModel(mdlName, tagName, stateName)
    
    outfitBaseObj = OutfitBase(origOutfitBaseObj.outfitName, mdlName)
    outfitBaseObj.bodyInt = stateObj.bodyInt
    outfitBaseObj.skinInt = stateObj.skinInt
    
    outfitBaseObj.stateName = stateName
    
    AddTagToOutfit(outfitBaseObj, tagName)
    
    bSaveFlexes = len(stateObj.flexes) > 0
    bSaveAllFlexes = stateObj.allFlexes
    
    for compObj in origOutfitBaseObj.outfitComponents:
        outfitBaseObj.outfitComponents.append(compObj)
        
    outfitBaseObj.flexes = origOutfitBaseObj.flexes
            
    # Then store this outfit in the dictionary, and save it to file.
    if mdlName not in OL2_Outfits:
        OL2_Outfits[mdlName] = {}
    
    if origOutfitBaseObj.outfitName in OL2_Outfits[mdlName]:
        # print OL2_Outfits[mdlName][origOutfitBaseObj.outfitName] == outfitBaseObj
        
        if OL2_Outfits[mdlName][origOutfitBaseObj.outfitName] == outfitBaseObj:
            return
    
    OL2_Outfits[mdlName][origOutfitBaseObj.outfitName] = outfitBaseObj 

    SaveOutfit(outfitBaseObj)
    
# Given an animation set, and an outfit name, equip an outfit, if one exists
def EquipOutfitByName(animSet, oName):
    mdlName = str(animSet.gameModel.modelName).lower()
    
    if mdlName not in OL2_Outfits:
        return None
    
    if oName not in OL2_Outfits[mdlName]:
        return None
        
    EquipOutfit(OL2_Outfits[mdlName][oName], animSet)
    
# Given an animset and an outfit name, delete that outfit, both from memory and from file, if it exists
def DeleteOutfitForAnimset(animSet, oName):
    global OL2_Outfits
    
    mdlName = str(animSet.gameModel.modelName).lower()
    
    if mdlName not in OL2_Outfits:
        return None
    
    if oName not in OL2_Outfits[mdlName]:
        return None
    
    os.remove(OL2_Outfits[mdlName][oName].hdFile) # Delete this outfit's hard-disk file
    del OL2_Outfits[mdlName][oName] # And delete it from memory
    
# Given an animset, and a candidate animset, as well as a list of bone names to ignore, will return True if the candidate animset has at least one bone in common with the animset.
def HasCommonBone(animSet, candSet, ignoreList):
    for ctrl in candSet.controls:
        if ctrl.GetTypeString() == "DmeTransformControl": # We only want bones
            if str(ctrl.name) in ignoreList:
                continue
                
            if animSet.FindControl(str(ctrl.name)):
                return True
                
    return False
    
def ConvertOFLtoOL2(fPath):
    global OL2_Outfits
    
    # First, convert the model-name. OFL saved the model name in the filepath, so this is how we get it back.
    fName = GetRawFileName(fPath).lower() + ".ofl"
    fName = str.replace(fName,".ofl",".mdl")
    fName = str.replace(fName,"-","/")
    fName = str.replace(fName,OFL_OutfitsFolderPath,"")
    
    # Then we load the OFL data itself, which is just a json dump
    with open(fPath,'r') as f:
        oflData = json.load(f)
    
    numOutfits = 0
    
    for oName in oflData:
        oData = oflData[oName]
        
        # Create the OL2 outfit object
        outfitBaseObj = OutfitBase(oName, fName)
        outfitBaseObj.skinInt = -1
        outfitBaseObj.bodyInt = oData.pop(0)
        
        # And then create components for everything else
        for compMdl in oData:
            outfitCompObj = OutfitComponent(compMdl) # No additional parameters to describe, so just the defaults work
            
            outfitBaseObj.outfitComponents.append(outfitCompObj)
            
        # Toss this into the outfits
        if fName not in OL2_Outfits:
            OL2_Outfits[fName] = {}
            
        OL2_Outfits[fName][oName] = outfitBaseObj
        
        numOutfits = numOutfits + 1
        
        # And then finally save this object out to file
        SaveOutfit(outfitBaseObj)
        
    return numOutfits
    
def ConvertAllOFLsToOL2():
    global OFL_OutfitsFolderPath
    
    if not os.path.exists(OFL_OutfitsFolderPath):
        mkdir_p(OFL_OutfitsFolderPath)
        
    numOutfits = 0
    
    for fName in os.listdir(OFL_OutfitsFolderPath):
        if os.path.isfile(OFL_OutfitsFolderPath + fName) and fName.endswith(".OFL"):
            numOutfits = numOutfits + ConvertOFLtoOL2(OFL_OutfitsFolderPath + fName)
            
    return numOutfits
    
# Testing
def Test():
    EquipOutfitByName(lizAnim, "test")
    
# UI Shit
def GetListItems(list):
    r = []
    
    for i in range(list.count()):
        r.append(list.item(i))
        
    return r
    
def ListStripeItems(lstItems):
    # Two different background colors, to stripe with.
    bg1 = QtGui.QColor()
    bg2 = QtGui.QColor()
    selClr = QtGui.QColor()
    
    bg1.setRgb(64,64,64)
    bg2.setRgb(80,80,80)
    selClr.setRgb(80,128,64)
    
    useBg = bg1
    
    for item in lstItems:
        if item.isSelected():
            item.setBackground(selClr)
        else:
            item.setBackground(useBg)
            
        if useBg == bg1:
            useBg = bg2
        else:
            useBg = bg1     
            
def LoadOutfitsIntoUI(listWidget):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()

    mdlName = str(animSet.gameModel.modelName)
    
    # If the model is already in memory, then just load it into memory
    if mdlName in OL2_Outfits:
        for OutfitName in OL2_Outfits[mdlName]:
            # Size hint
            hint = QtCore.QSize()
            hint.setHeight(24)
            
            # Create the new Outfit's item
            item = QtGui.QListWidgetItem()
            item.setData(QtCore.Qt.UserRole,OutfitName)
            item.setText(OutfitName)
            item.setSizeHint(hint)
            
            listWidget.addItem(item)
            
def AddCandidateAnimsetsToList(list):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()

    animSetName = str(animSet.name).lower().strip()
    for candSet in shot.animationSets:
        candSetName = str(candSet.name).lower().strip()
        
        # Can't add the model as an outfit to itself, silly!
        if candSetName == animSetName:
            continue
            
        # Go through all of the candidate's bones and see if it has at least one non-root bone in common with the animset.
        if HasCommonBone(animSet,candSet, ["rootTransform"]):
            # Size hint
            hint = QtCore.QSize()
            hint.setHeight(24)
            
            # Create the new Outfit's item
            item = QtGui.QListWidgetItem()
            item.setData(QtCore.Qt.UserRole,candSet)
            item.setText(candSetName)
            item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
            item.setCheckState(QtCore.Qt.Unchecked)
            item.setSizeHint(hint)
            
            list.addItem(item)   

def PopulateTagsList(cmbList, bAddNoTags=False):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    tagNames = GetTagsFromTagStates(GetTagStatesForAnimset(animSet))
    
    for tagName in tagNames:
        cmbList.addItem(tagName)
        
    if len(tagNames) < 1 and bAddNoTags:
        cmbList.addItem("(No tags)")

def PopulateStatesList(cmbList, bAddNoStates=False):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    stateNames = GetStatesFromTagStates(GetTagStatesForAnimset(animSet))
    
    for stateName in stateNames:
        cmbList.addItem(stateName)
        
    if len(stateNames) < 1 and bAddNoStates:
        cmbList.addItem("(No states)")        
                 
def onItemSelectionChanged(list):    
    # Restripe all of the items, highlighting what all is selected
    ListStripeItems(GetListItems(list))       
    
def onLoadOutfit(uxElems, parentWindow):
    global OL_BindFlexes
    global OL_UseLock
    global OL_ScaleOutfit
    
    list = uxElems["list"]
    chkBind = uxElems["chkBind"]
    chkLock = uxElems["chkLock"]
    chkScale = uxElems["chkScale"]
    
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    OL_BindFlexes = chkBind.isChecked()
    OL_UseLock = chkLock.isChecked()
    OL_ScaleOutfit = chkScale.isChecked()
    
    for item in list.selectedItems():
        EquipOutfitByName(animSet,item.data(QtCore.Qt.UserRole))
    
    parentWindow.close()
    
def onDeleteOutfit(list, parentWindow):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    selItem = list.selectedItems()[0]
    
    DeleteOutfitForAnimset(animSet,selItem.data(QtCore.Qt.UserRole))
    
    # Update the list
    list.clear()
    LoadOutfitsIntoUI(list)
    list.sortItems()
    ListStripeItems(GetListItems(list)) 
    
    
def onSaveOutfit(list, oName, bUseBodygroup, bUseSkin, bSaveFlexes, bSaveAllFlexes, cmbTag, cmbState, window, outfitsList):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()

    # Collect all of the selected animsets
    oAnimsets = []
    
    for item in GetListItems(list):
        if item.checkState() == QtCore.Qt.Checked:
            oAnimsets.append(item.data(QtCore.Qt.UserRole))
          
    # Collect the selected tag & state, if they're being used.
    tagName = cmbTag.currentText()
    stateName = cmbState.currentText()
    
    # Create the outfit; if we're using tag/state, then we need to create it using the more complex method
    if (cmbTag.currentIndex() > 0 and cmbState.currentIndex() > 0):
        CreateOutfitWithState(animSet, oName, oAnimsets, tagName, stateName)
        
        MapStatedOutfitsWithTag(tagName)
    else:
        CreateOutfit(animSet, oName, oAnimsets, bUseBodygroup, bUseSkin, bSaveFlexes, bSaveAllFlexes)
    
    # Update the list
    outfitsList.clear()
    
    LoadOutfitsIntoUI(outfitsList)
    outfitsList.sortItems()
    ListStripeItems(GetListItems(outfitsList))
    
    # And close the window
    window.close()
    
def onAddTag(listTags, txtTag, window):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    tagName = txtTag.text().strip()
    
    # Get current TagState
    curTagState = GetOperatingTagState(animSet, tagName)     
    
    # Add this tag to the TagState.
    curTagState.tags.append(tagName)
    
    # And then repopulate the list
    listTags.clear()
    PopulateTagsList(listTags)
    ListStripeItems(GetListItems(listTags))
    
    # Finally, save this TagState out to file
    SaveTagState(curTagState)
    
def onAddState(listStates, txtState, bBodygroups, bSkins, bSaveFlexes, bSaveAllFlexes, window):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    # Get current TagState
    curTagState = GetOperatingTagState(animSet) 
    stateName = txtState.text().strip()

    # Add this state to the TagState
    AddStateToTagState(curTagState, stateName, animSet, bBodygroups, bSkins, bSaveFlexes, bSaveAllFlexes)
    
    # And then repopulate the list
    listStates.clear()
    PopulateStatesList(listStates)
    ListStripeItems(GetListItems(listStates))
    
    # Finally, save this TagState out to file
    SaveTagState(curTagState)  

def onRemoveTag(listTags):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    # Grab the selected item and the operating tag, and then remove the former from the latter
    selItem = listTags.selectedItems()[0]
    
    curTagState = GetOperatingTagState(animSet)
    
    RemoveTagFromTagState(curTagState, selItem.text())
    
    # Then repopulate the list
    listTags.clear()
    PopulateTagsList(listTags)
    ListStripeItems(GetListItems(listTags))
    
    # Finally, save this TagState out to file
    SaveTagState(curTagState)

def onRemoveState(listStates):
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    # Grab the selected item and the operating tag, and then remove the former from the latter
    selItem = listStates.selectedItems()[0]
    
    curTagState = GetOperatingTagState(animSet)
    
    RemoveStateFromTagState(curTagState, selItem.text())
    
    # Then repopulate the list
    listStates.clear()
    PopulateStatesList(listStates)
    ListStripeItems(GetListItems(listStates))
    
    # Finally, save this TagState out to file
    SaveTagState(curTagState)      
    
def onReloadEverything(window):
    ReloadEverything()
    
    window.close()
    
def CreateSaveUI(outfitsList):
    # Create the UI window
    window = QtGui.QDialog()
    window.resize(180,400)
    window.setWindowTitle("Create Outfit")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 128)

    # Outfit name
    txtName = QtGui.QLineEdit()
    txtName.setText("Outfit name")
    grid.addWidget(txtName, 0, 0, 1, 3)
    
    # List of viable animsets
    lblAnimSets = QtGui.QLabel()
    lblAnimSets.setText("Select the animsets to make outfit from.")
    grid.addWidget(lblAnimSets, 1, 0, 1, 3)
    
    list = QtGui.QListWidget()
    list.setSpacing(2)
    list.itemSelectionChanged.connect(lambda: onItemSelectionChanged(list))
    list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
    
    AddCandidateAnimsetsToList(list)
    ListStripeItems(GetListItems(list)) 
    
    grid.addWidget(list, 2, 0, 1, 3)
    
    # Options
    lblOptions = QtGui.QLabel()
    lblOptions.setText("NOTE: The first two options only apply to the base model.\nOutfit components will ALWAYS save skin & bodygroup.\nFlex options apply to both.")
    grid.addWidget(lblOptions, 3, 0, 1, 3)  
    
    # Bodygroup & Skin checkboxes
    chkBodygroup = QtGui.QCheckBox()
    chkBodygroup.setText("Use Bodygroups?")
    chkBodygroup.setChecked(True)
    grid.addWidget(chkBodygroup, 4, 0, 1, 1)
    
    chkSkin = QtGui.QCheckBox()
    chkSkin.setText("Use Skins?")
    chkSkin.setChecked(True)
    grid.addWidget(chkSkin, 4, 1, 1, 1)    
    
    # Flex options
    chkFlexes = QtGui.QCheckBox()
    chkFlexes.setText("Use Flexes? (!!)")
    chkFlexes.setChecked(False)
    chkFlexes.setToolTip("This will save and force-set all non-0.0 flexes. It will only save the last-keyframed value.")
    grid.addWidget(chkFlexes, 5, 0, 1, 1)
    
    chkAllFlexes = QtGui.QCheckBox()
    chkAllFlexes.setText("Save ALL flexes? (!!)")
    chkAllFlexes.setChecked(False)
    chkAllFlexes.setToolTip("This will save and force-set all 0.0 flexes. This can result in large and slow-to-load files.\nNOT RECOMMENDED unless you know what you're doing!")
    grid.addWidget(chkAllFlexes, 5, 1, 1, 1)
    
    # Tag & State comboboxes
    cmbTag = QtGui.QComboBox()
    cmbTag.addItem("Don't Use Tags")
    cmbTag.setEditable(False)
    PopulateTagsList(cmbTag)
    grid.addWidget(cmbTag, 6, 0, 1, 1)
    
    cmbState = QtGui.QComboBox()
    cmbState.addItem("Don't Use States")
    cmbState.setEditable(False)
    PopulateStatesList(cmbState)
    grid.addWidget(cmbState, 6, 1, 1, 1)
    
    # Save button
    btnSave = QtGui.QPushButton()
    btnSave.setText("Save")
    btnSave.clicked.connect(lambda: onSaveOutfit(list, str(txtName.text()), chkBodygroup.isChecked(), chkSkin.isChecked(), chkFlexes.isChecked(), chkAllFlexes.isChecked(), cmbTag, cmbState, window, outfitsList))
    grid.addWidget(btnSave, 7, 0, 1, 3)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()
    
def CreateOFLConversionUI():
    # Run the code
    numOutfits = ConvertAllOFLsToOL2()
    
    # And then create the UI window
    window = QtGui.QDialog()
    window.resize(100,100)
    window.setWindowTitle("OFL to OL2 Converter")
    window.setWindowFlags(QtCore.Qt.FramelessWindowHint)

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 128)

    # Converter text
    lblFeedback = QtGui.QLabel()
    lblFeedback.setText("Converted " + (str(numOutfits)) + " outfits to OL2.")
    grid.addWidget(lblFeedback, 0, 0, 1, 3)
    
    # Continue button
    btnCont = QtGui.QPushButton()
    btnCont.setText("Continue")
    btnCont.clicked.connect(lambda: window.close())
    grid.addWidget(btnCont, 1, 0, 1, 3)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()
    
def CreateModelTagStateUI():
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    mdlNameDisplay = GetRawFileName(str(animSet.gameModel.modelName)).lower().strip()
    
    # Create the UI window
    window = QtGui.QDialog()
    window.resize(180,400)
    window.setWindowTitle("Manage Model Tags & States")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 86)
    grid.setColumnMinimumWidth(2, 86)

    # Model name, for reference
    txtName = QtGui.QLineEdit()
    txtName.setText(mdlNameDisplay)
    txtName.setEnabled(False)
    grid.addWidget(txtName, 0, 0, 1, 4)
    
    # Tags side
    # List of existing tags for model
    lblTags = QtGui.QLabel()
    lblTags.setText("Existing Tags")
    grid.addWidget(lblTags, 1, 0, 1, 1)
    
    listTags = QtGui.QListWidget()
    listTags.setSpacing(2)
    listTags.itemSelectionChanged.connect(lambda: onItemSelectionChanged(listTags))
    listTags.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
    
    PopulateTagsList(listTags, True)
    ListStripeItems(GetListItems(listTags))
    grid.addWidget(listTags, 2, 0, 3, 2)
    
    # Add tag
    txtTag = QtGui.QLineEdit()
    txtTag.setText("New Tag")
    grid.addWidget(txtTag, 5, 0, 1, 1)
    
    btnAddTag = QtGui.QPushButton()
    btnAddTag.setText("Add")
    btnAddTag.clicked.connect(lambda: onAddTag(listTags, txtTag, window))
    grid.addWidget(btnAddTag, 5, 1, 1, 1)
    
    # Remove tag
    btnRemoveTag = QtGui.QPushButton()
    btnRemoveTag.setText("Remove Selected")
    btnRemoveTag.clicked.connect(lambda: onRemoveTag(listTags))
    grid.addWidget(btnRemoveTag, 6, 0, 1, 2)
    
    # Skins side
    # List of existing states for model
    lblTags = QtGui.QLabel()
    lblTags.setText("Existing States")
    grid.addWidget(lblTags, 1, 2, 1, 1)
    
    listStates = QtGui.QListWidget()
    listStates.setSpacing(2)
    listStates.itemSelectionChanged.connect(lambda: onItemSelectionChanged(listStates))
    listStates.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
    
    PopulateStatesList(listStates, True)
    ListStripeItems(GetListItems(listStates))
    grid.addWidget(listStates, 2, 2, 1, 3)    
    
    # Bodygroup & Skin checkboxes
    chkBodygroup = QtGui.QCheckBox()
    chkBodygroup.setText("Use Bodygroups?")
    chkBodygroup.setChecked(True)
    grid.addWidget(chkBodygroup, 3, 2, 1, 1)
    
    chkSkin = QtGui.QCheckBox()
    chkSkin.setText("Use Skins?")
    chkSkin.setChecked(True)
    grid.addWidget(chkSkin, 3, 3, 1, 1)  
    
    # Flex check boxes
    chkFlexes = QtGui.QCheckBox()
    chkFlexes.setText("Save Flexes")
    chkFlexes.setChecked(False)
    grid.addWidget(chkFlexes, 4, 2, 1, 1)
    
    chkAllFlexes = QtGui.QCheckBox()
    chkAllFlexes.setText("ALL Flexes")
    chkAllFlexes.setChecked(False)
    grid.addWidget(chkAllFlexes, 4, 3, 1, 1)
   
    chkFlexes.setToolTip("This will save and force-set all non-0.0 flexes. It will only save the last-keyframed value.")
    chkAllFlexes.setToolTip("This will save and force-set all 0.0 flexes. This can result in large and slow-to-load files.\nNOT RECOMMENDED unless you know what you're doing!")
    
    # Add state
    txtState = QtGui.QLineEdit()
    txtState.setText("New State")
    grid.addWidget(txtState, 5, 2, 1, 1)
    
    btnAddState = QtGui.QPushButton()
    btnAddState.setText("Add")
    btnAddState.clicked.connect(lambda: onAddState(listStates, txtState, chkBodygroup.isChecked(), chkSkin.isChecked(), chkFlexes.isChecked(), chkAllFlexes.isChecked(), window))
    grid.addWidget(btnAddState, 5, 3, 1, 1)
    
    # Remove state
    btnRemoveState = QtGui.QPushButton()
    btnRemoveState.setText("Remove Selected")
    btnRemoveState.clicked.connect(lambda: onRemoveState(listStates))
    grid.addWidget(btnRemoveState, 6, 2, 1, 2)    
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()    
    
def onSelectAnimset(list,window):
    global CurAnimSet
    
    uiList = GetListItems(list)
    
    for i in range(0, len(uiList)):
        uiItem = uiList[i]
        
        if uiItem.checkState() is QtCore.Qt.CheckState.Checked:
            CurAnimSet = uiItem.data(QtCore.Qt.UserRole)
            
            window.close()
            
            CreateUI()
            
def onTransposeCopy(list, window):
    uiList = GetListItems(list)
    
    lstAnimsets = []
    
    for i in range(0, len(uiList)):
        uiItem = uiList[i]
        
        if uiItem.checkState() is QtCore.Qt.CheckState.Checked:
            animset = uiItem.data(QtCore.Qt.UserRole)
            
            lstAnimsets.append(animset)
            
    CopyOutfitDataForTransposition(lstAnimsets)
    
    window.close()
    
def onApplyTransposition(window):
    window.close()
    
    TransposeStoredOutfits()
    
def AddCandidateAnimsetsToList(list):
    shot = sfmClipEditor.GetSelectedShots()[0]
    
    for candSet in shot.animationSets:
        candSetName = str(candSet.name).lower().strip()

        if hasattr(candSet,"gameModel"):
            # Size hint
            hint = QtCore.QSize()
            hint.setHeight(24)
            
            # Create the new Outfit's item
            item = QtGui.QListWidgetItem()
            item.setData(QtCore.Qt.UserRole,candSet)
            item.setText(candSetName)
            item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
            item.setCheckState(QtCore.Qt.Unchecked)
            item.setSizeHint(hint)
            
            list.addItem(item)   
            
def CreateAnimsetUI():
    # Create the UI window
    window = QtGui.QDialog()
    window.resize(180,400)
    window.setWindowTitle("Select Model")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 128)

    # List of viable animsets
    lblAnimSets = QtGui.QLabel()
    lblAnimSets.setText("Select the model to run Outfit Loader on.")
    grid.addWidget(lblAnimSets, 1, 0, 1, 3)
    
    list = QtGui.QListWidget()
    list.setSpacing(2)
    #list.itemSelectionChanged.connect(lambda: onItemSelectionChanged(list))
    list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
    
    AddCandidateAnimsetsToList(list)
    #ListStripeItems(GetListItems(list)) 
    
    grid.addWidget(list, 2, 0, 1, 3)
    
    # Save button
    btnSave = QtGui.QPushButton()
    btnSave.setText("Run Outfit Loader")
    btnSave.clicked.connect(lambda: onSelectAnimset(list, window))
    grid.addWidget(btnSave, 7, 0, 1, 3)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()    
    
def CreateUI_TransposeCopy(parentWindow):
    parentWindow.close()
    
    # Create the UI window
    window = QtGui.QDialog()
    window.resize(180,400)
    window.setWindowTitle("Select Models")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 128)

    # List of viable animsets
    lblAnimSets = QtGui.QLabel()
    lblAnimSets.setText("Select the models to copy outfit data from.")
    grid.addWidget(lblAnimSets, 1, 0, 1, 3)
    
    list = QtGui.QListWidget()
    list.setSpacing(2)
    list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
    
    candidateAnimSets = GetOutfitTransposeableAnimsets()
    
    for candSet in candidateAnimSets:
        candSetName = str(candSet.name).lower().strip()

        if hasattr(candSet,"gameModel"):
            # Size hint
            hint = QtCore.QSize()
            hint.setHeight(24)
            
            # Create the new Outfit's item
            item = QtGui.QListWidgetItem()
            item.setData(QtCore.Qt.UserRole,candSet)
            item.setText(candSetName)
            item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
            item.setCheckState(QtCore.Qt.Unchecked)
            item.setSizeHint(hint)
            
            list.addItem(item)  
            
    #AddCandidateAnimsetsToList(list)
    #ListStripeItems(GetListItems(list)) 
    
    grid.addWidget(list, 2, 0, 1, 3)
    
    # Save button
    btnSave = QtGui.QPushButton()
    btnSave.setText("Copy Selected Models' Outfit Data")
    btnSave.clicked.connect(lambda: onTransposeCopy(list, window))
    grid.addWidget(btnSave, 7, 0, 1, 3)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()    
    
def CreateUI_Transposition(parentWindow):
    parentWindow.close()
    
    # Create the UI window
    window = QtGui.QDialog()
    window.resize(260,1)
    window.setWindowTitle("Outfit Transposition")

    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 260)

    # Buttons
    btnCopy = QtGui.QPushButton()
    btnCopy.setText("Select Transposition Targets")
    btnCopy.setToolTip("Bring up an interface listing all of the models in the active shot that have outfits that can be transposed.\nSelect the models from this list whose outfits you would like to transpose to other shots.")
    btnCopy.clicked.connect(lambda: CreateUI_TransposeCopy(window))
    grid.addWidget(btnCopy, 0, 0, 1, 1)
    
    btnPaste = QtGui.QPushButton()
    btnPaste.setText("Apply Transpositions")
    btnPaste.setToolTip("Apply the last-copied outfit transpositions to the currently selected scene.")
    btnPaste.clicked.connect(lambda: onApplyTransposition(window))
    grid.addWidget(btnPaste, 1, 0, 1, 1)
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()    
    

def CreateUI():
    shot = sfm.GetCurrentShot()
    animSet = GetCurAnimset()
    
    if not hasattr(animSet,"gameModel"):
        CreateAnimsetUI()
        return 0
        
    uxElems = {}
    
    # Create the base UI window
    window = QtGui.QDialog()
    window.resize(240,370)
    window.setWindowTitle("Outfit Loader v3.5")
    
    grid = QtGui.QGridLayout()
    grid.setSpacing(12)
    grid.setColumnMinimumWidth(0, 72)
    grid.setColumnMinimumWidth(1, 72)
    grid.setColumnMinimumWidth(2, 72)

    # Label
    lblWarning = QtGui.QLabel()
    lblWarning.setText("NOTE: This window will close on load.\nSelect all the outfits you want to load with Ctrl-Click.")
    grid.addWidget(lblWarning, 0, 0, 1, 3)
    
    # Outfits list
    list = QtGui.QListWidget()
    list.setSpacing(2)
    list.itemSelectionChanged.connect(lambda: onItemSelectionChanged(list))
    list.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
    uxElems["list"] = list
    
    #LoadEverythingInitialize() # Load everything into memory, but only if the memory is empty.
    LoadDataInitialize() # Load only the relevant model's data into memory, but only if it hasn't been loaded already. This is because the above method was starting to take literal seconds.
    LoadOutfitsIntoUI(list) # And then load them into the list
    list.sortItems()
    ListStripeItems(GetListItems(list)) 
    
    grid.addWidget(list, 1, 0, 1, 3)
    
    chkBind = QtGui.QCheckBox()
    chkBind.setText("Bind Flexes?")
    chkBind.setToolTip("If enabled, then any flexes that the outfit and host model share will be AUTOMATICALLY set on the outfit, to reflect them on the host model. You CANNOT set these flexes on the outfit manually.")
    chkBind.setChecked(OL_BindFlexes)
    grid.addWidget(chkBind, 2, 0, 1, 1)
    uxElems["chkBind"] = chkBind
    
    chkLock = QtGui.QCheckBox()
    chkLock.setText("Use Locks?")
    chkLock.setToolTip("If enabled, then the outfit will be attached to the host using locks. If disabled, it will be attached using constraints.\n\nConstraints are faster, but CANNOT be scaled and CANNOT be moved after the outfit is attached.\nLocks are slower, but CAN be scaled, and CAN be moved.\n\nUnless you need to constantly attach and remove outfits, keeping this checked and using locks is HIGHLY recommended.")
    chkLock.setChecked(OL_UseLock)
    grid.addWidget(chkLock, 2, 1, 1, 1)
    uxElems["chkLock"] = chkLock
    
    chkScale = QtGui.QCheckBox()
    chkScale.setText("Scale Outfit?")
    chkScale.setToolTip("If enabled, then the outfit will be automatically scaled to fit the host model.\n\nThis is computed by determining the distance difference between the first two\nbones on the host model and on the outfit model, and then comparing the two distances.\n\nIf Locks are disabled, then scaling WILL NOT WORK.\n\nIn general, keeping this checked is HIGHLY recommended.")
    chkScale.setChecked(OL_ScaleOutfit)
    grid.addWidget(chkScale, 2, 2, 1, 1)
    uxElems["chkScale"] = chkScale
    
    # Outfit controls
    btnLoad = QtGui.QPushButton()
    btnLoad.setText("Load Outfit")
    btnLoad.clicked.connect(lambda: onLoadOutfit(uxElems, window))
    grid.addWidget(btnLoad, 3, 0, 1, 1)
    
    btnSave = QtGui.QPushButton()
    btnSave.setText("Create Outfit")
    btnSave.setToolTip("Will prompt you to select models in the scene to create an outfit from.\nThe model you ran this script on will be the host model.")
    btnSave.clicked.connect(lambda: CreateSaveUI(list))
    grid.addWidget(btnSave, 3, 1, 1, 1)
    
    btnDelete = QtGui.QPushButton()
    btnDelete.setText("Delete Outfit")
    btnDelete.clicked.connect(lambda: onDeleteOutfit(list, window))
    grid.addWidget(btnDelete, 3, 2, 1, 1)
    
    # Manage model & reload outfits
    btnManage = QtGui.QPushButton()
    btnManage.setText("Manage Model Tags / States")
    btnManage.setToolTip("Tags and States are used to distribute outfits among similar models.\nAny models that share both Tag and State will get all outfits similarly attached to those Tags and States.")
    btnManage.clicked.connect(lambda: CreateModelTagStateUI())
    grid.addWidget(btnManage, 4, 0, 1, 2)
    
    btnReload = QtGui.QPushButton()
    btnReload.setText("Reload Outfits")
    btnReload.setToolTip("Will go through all models and outfits that have any OL data, and assign all outfits with the same Tag & State as models.\nThis can take a while. SFM may act as if it had crashed while processing.\n\nThis will close the Outfit Loader window.")
    btnReload.clicked.connect(lambda: onReloadEverything(window))
    grid.addWidget(btnReload, 4, 2, 1, 1)
    
    btnTranspose = QtGui.QPushButton()
    btnTranspose.setText("Outfit Transposition")
    btnTranspose.setToolTip("Brings up the interface for the outfit transposition system. From this menu, you can choose what outfits to copy for transposition,\nand you can apply the currently-copied transposition to the currently selected shot.")
    btnTranspose.clicked.connect(lambda: CreateUI_Transposition(window))
    grid.addWidget(btnTranspose, 5, 1, 1, 2)    
    
    # Display the window
    window.setLayout(grid)
    window.setModal(True)
    window.exec_()
    

#Test()

CreateUI()