NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

paperDollDataManagement.py (65508B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\common\script\paperDoll\paperDollDataManagement.py
      2 from __future__ import print_function
      3 import os
      4 import uthread
      5 import copy
      6 import types
      7 import hashlib
      8 import blue
      9 import eve.common.script.paperDoll.paperDollDefinitions as pdDef
     10 import eve.common.script.paperDoll.paperDollCommonFunctions as pdCF
     11 from eve.common.script.paperDoll.paperDollResData import ResData, GenderData
     12 import telemetry
     13 import log
     14 import sys
     15 import legacy_r_drive
     16 from .yamlPreloader import YamlPreloader, LoadYamlFileNicely
     17 from .paperDollConfiguration import PerformanceOptions
     18 from .yamlPreloader import AvatarPartMetaData
     19 MAP_CACHE_PATH = u'cache:/Avatars/cachedMaps'
     20 
     21 def ClearAllCachedMaps():
     22     fileSystemPath = blue.paths.ResolvePath(MAP_CACHE_PATH)
     23     for root, dirs, files in os.walk(fileSystemPath):
     24         for f in files:
     25             os.remove(os.path.join(root, f))
     26 
     27 
     28 def GetCacheFilePath(cachePath, hashKey, textureWidth, mapType):
     29     cacheFilePath = u''
     30     hashKey = str(hashKey).replace('-', '_')
     31     return u'{0}/{1}/{2}{3}.dds'.format(cachePath, textureWidth, mapType, hashKey)
     32 
     33 
     34 def FindCachedMap(hashKey, textureWidth, mapType):
     35     resPath = GetCacheFilePath(MAP_CACHE_PATH, hashKey, textureWidth, mapType)
     36     if blue.paths.exists(resPath):
     37         rotPath = blue.paths.ResolvePath(resPath)
     38         os.utime(rotPath, None)
     39         cachedTexture = blue.resMan.GetResource(resPath)
     40         return cachedTexture
     41 
     42 
     43 def SaveMapsToDisk(hashKey, maps):
     44     for i, each in enumerate(maps):
     45         if each is not None:
     46             textureWidth = each.width
     47             filePath = blue.paths.ResolvePathForWriting(GetCacheFilePath(MAP_CACHE_PATH, hashKey, textureWidth, i))
     48             if filePath:
     49                 folder = os.path.split(filePath)[0]
     50                 try:
     51                     if not os.path.exists(folder):
     52                         os.makedirs(folder)
     53                     each.Save(filePath)
     54                 except OSError:
     55                     pass
     56 
     57 
     58 class ModifierLoader():
     59     __sharedLoadSource = False
     60     __sharedResData = None
     61     __isLoaded = False
     62 
     63     def setclothSimulationActive(self, value):
     64         self._clothSimulationActive = value
     65 
     66     clothSimulationActive = property(fget=lambda self: self._clothSimulationActive, fset=lambda self, value: self.setclothSimulationActive(value))
     67 
     68     def __init__(self):
     69         self.yamlPreloader = YamlPreloader()
     70         self.resData = None
     71         self.patterns = []
     72         self.IsLoaded = False
     73         self.forceRunTimeOptionGeneration = False
     74         self._clothSimulationActive = False
     75         uthread.new(self.LoadData_t)
     76 
     77     def DebugReload(self, forceRunTime = False):
     78         self.forceRunTimeOptionGeneration = forceRunTime
     79         ModifierLoader.__sharedLoadSource = False
     80         ModifierLoader.__sharedResData = None
     81         ModifierLoader.__isLoaded = False
     82         YamlPreloader.Clear()
     83         uthread.new(self.LoadData_t)
     84 
     85     @staticmethod
     86     @telemetry.ZONE_FUNCTION
     87     def LoadBlendshapeLimits(limitsResPath):
     88         data = LoadYamlFileNicely(limitsResPath)
     89         if data is not None:
     90             limits = data.get('limits')
     91             ret = {}
     92             if limits and type(limits) is dict:
     93                 for k, v in limits.iteritems():
     94                     ret[k.lower()] = v
     95 
     96                 data['limits'] = ret
     97         return data
     98 
     99     @telemetry.ZONE_METHOD
    100     def GetWorkingDirectory(self, gender):
    101         if gender == pdDef.GENDER.FEMALE:
    102             return pdDef.FEMALE_BASE_PATH
    103         else:
    104             return pdDef.MALE_BASE_PATH
    105 
    106     @telemetry.ZONE_METHOD
    107     def LoadData_t(self):
    108         try:
    109             if ModifierLoader.__sharedLoadSource == legacy_r_drive.loadFromContent and ModifierLoader.__sharedResData:
    110                 self.resData = ModifierLoader.__sharedResData
    111                 while not ModifierLoader.__isLoaded:
    112                     pdCF.Yield()
    113 
    114                 self.IsLoaded = True
    115                 return
    116             self.resData = ResData()
    117             femPath = pdDef.FEMALE_BASE_PATH
    118             femPathLOD = pdDef.FEMALE_BASE_LOD_PATH
    119             femPathGT = pdDef.FEMALE_BASE_GRAPHICS_TEST_PATH
    120             femPathGTLOD = pdDef.FEMALE_BASE_GRAPHICS_TEST_LOD_PATH
    121             malePath = pdDef.MALE_BASE_PATH
    122             malePathLOD = pdDef.MALE_BASE_LOD_PATH
    123             malePathGT = pdDef.MALE_BASE_GRAPHICS_TEST_PATH
    124             malePathGTLOD = pdDef.MALE_BASE_GRAPHICS_TEST_LOD_PATH
    125             if legacy_r_drive.loadFromContent:
    126                 femPath = femPath.replace('res:', 'resPreview:')
    127                 femPathLOD = femPathLOD.replace('res:', 'resPreview:')
    128                 femPathGT = femPathGT.replace('res:', 'resPreview:')
    129                 femPathGTLOD = femPathGTLOD.replace('res:', 'resPreview:')
    130                 malePath = malePath.replace('res:', 'resPreview:')
    131                 malePathLOD = malePathLOD.replace('res:', 'resPreview:')
    132                 malePathGT = malePathGT.replace('res:', 'resPreview:')
    133                 malePathGTLOD = malePathGTLOD.replace('res:', 'resPreview:')
    134             self.resData.Populate(pdDef.GENDER.FEMALE, femPath)
    135             self.resData.Populate(pdDef.GENDER.FEMALE, femPathLOD, key=GenderData.LOD_KEY)
    136             self.resData.Populate(pdDef.GENDER.FEMALE, femPathGT, key=GenderData.TEST_KEY)
    137             self.resData.Populate(pdDef.GENDER.FEMALE, femPathGTLOD, key=GenderData.TEST_LOD_KEY)
    138             self.resData.Populate(pdDef.GENDER.MALE, malePath)
    139             self.resData.Populate(pdDef.GENDER.MALE, malePathLOD, key=GenderData.LOD_KEY)
    140             self.resData.Populate(pdDef.GENDER.MALE, malePathGT, key=GenderData.TEST_KEY)
    141             self.resData.Populate(pdDef.GENDER.MALE, malePathGTLOD, key=GenderData.TEST_LOD_KEY)
    142             while self.resData.IsLoading():
    143                 pdCF.Yield()
    144 
    145             PerformanceOptions.SetEnableYamlCache(True)
    146             while self.yamlPreloader.IsLoading():
    147                 pdCF.Yield()
    148 
    149             for gender in pdDef.GENDER:
    150                 self.resData.PopulateVirtualModifierFolders(gender)
    151 
    152             self.resData.LinkSiblings(None, GenderData.LOD_KEY)
    153             self.resData.LinkSiblings(GenderData.TEST_KEY, GenderData.TEST_LOD_KEY)
    154             self.resData.PropogateLODRules()
    155             self._LoadPatterns()
    156             self.IsLoaded = True
    157         except Exception as e:
    158             import logging
    159             logging.error(e)
    160             print(e)
    161         finally:
    162             ModifierLoader.__sharedLoadSource = legacy_r_drive.loadFromContent
    163             ModifierLoader.__sharedResData = self.resData
    164             ModifierLoader.__isLoaded = self.IsLoaded
    165 
    166     @telemetry.ZONE_METHOD
    167     def WaitUntilLoaded(self):
    168         while not self.IsLoaded:
    169             pdCF.Yield()
    170 
    171     @telemetry.ZONE_METHOD
    172     def _LoadPatterns(self):
    173         if legacy_r_drive.loadFromContent:
    174             self.patterns = pdDef.GetPatternList()
    175         else:
    176             if pdDef.GENDER_ROOT:
    177                 optionsFile = 'res:/{0}/Character/PatternOptions.yaml'.format(pdDef.BASE_GRAPHICS_FOLDER)
    178             else:
    179                 optionsFile = 'res:/{0}/Character/Modular/PatternOptions.yaml'.format(pdDef.BASE_GRAPHICS_FOLDER)
    180             self.patterns = LoadYamlFileNicely(optionsFile)
    181 
    182     def GetPatternDir(self):
    183         return 'res:/{0}/Character/Patterns'.format(pdDef.BASE_GRAPHICS_FOLDER)
    184 
    185     @telemetry.ZONE_METHOD
    186     def GetItemType(self, itemTypePath, gender = None):
    187         if gender and 'res:' not in itemTypePath:
    188             basePath = ''
    189             if gender == pdDef.GENDER.MALE:
    190                 basePath = pdDef.MALE_BASE_PATH
    191             elif gender == pdDef.GENDER.FEMALE:
    192                 basePath = pdDef.FEMALE_BASE_PATH
    193             if itemTypePath.startswith('/'):
    194                 fmt = '{0}{1}'
    195             else:
    196                 fmt = '{0}/{1}'
    197             itemTypePath = fmt.format(basePath, itemTypePath)
    198         itemType = self.__GetFromYamlCache(itemTypePath)
    199         return itemType
    200 
    201     def ListTypes(self, gender, cat = None, modifier = None):
    202         availableTypes = []
    203         if modifier:
    204             parentEntry = self.resData.GetEntryByPath(gender, modifier.respath)
    205             entries = self.resData.GetChildEntries(parentEntry)
    206         elif cat:
    207             parentEntry = self.resData.GetEntryByPath(gender, cat)
    208             entries = self.resData.Traverse(parentEntry, visitFunc=lambda x: not x.dirs)
    209         else:
    210             entries = [ entry for entry in self.resData.GetEntriesByGender(gender) if not entry.dirs ]
    211         for entry in entries:
    212             fileNames = entry.GetFilesByExt('type')
    213             for fn in fileNames:
    214                 fullResPath = entry.GetFullResPath(fn)
    215                 availableTypes.append(fullResPath)
    216 
    217         log.LogNotice('paperdollDataMgmt: List types -> (%s, %s, %s, %s)' % (gender,
    218          cat,
    219          modifier,
    220          len(availableTypes)))
    221         return availableTypes
    222 
    223     def CategoryHasTypes(self, category):
    224 
    225         def Visit(gender, resDataEntry):
    226             if not resDataEntry:
    227                 return False
    228             found = False
    229             if 'types' in resDataEntry.dirs:
    230                 found = True
    231             if not found:
    232                 for childResDataEntry in self.resData.GetChildEntries(resDataEntry):
    233                     found = Visit(gender, childResDataEntry)
    234                     if found:
    235                         break
    236 
    237             return found
    238 
    239         def CheckTypes(gender):
    240             entry = self.resData.GetEntryByPath(gender, category)
    241             return Visit(gender, entry)
    242 
    243         return any(map(lambda x: CheckTypes(x), pdDef.GENDER))
    244 
    245     @telemetry.ZONE_METHOD
    246     def ListOptions(self, gender, cat = None, showVariations = False):
    247         results = []
    248         entries = []
    249         if cat:
    250             pathEntry = self.resData.GetEntryByPath(gender, cat)
    251             if pathEntry:
    252                 entries.extend(self.resData.GetChildEntries(pathEntry))
    253             pathEntry = self.resData.GetEntryByPath(gender, cat, key=GenderData.TEST_KEY)
    254             if pathEntry:
    255                 entries.extend(self.resData.GetChildEntries(pathEntry, key=GenderData.TEST_KEY))
    256             if showVariations:
    257                 for entry in entries:
    258                     variations = []
    259                     for childEntry in self.resData.GetChildEntries(entry):
    260                         if childEntry.isVariationFolder:
    261                             variations.append(childEntry.leafFolderName)
    262 
    263                     result = (entry.leafFolderName, tuple(variations))
    264                     results.append(result)
    265 
    266             else:
    267                 for entry in entries:
    268                     results.append(entry.leafFolderName)
    269 
    270         else:
    271             results.extend(self.resData.GetPathToEntryByGender(gender).keys())
    272             results.extend(self.resData.GetPathToEntryByGender(gender, key=GenderData.TEST_KEY).keys())
    273         results.sort()
    274         return results
    275 
    276     @telemetry.ZONE_METHOD
    277     def CollectBuildData(self, gender, path, weight = 1.0, forceLoaded = False):
    278         currentBuildData = BuildData(path)
    279         currentBuildData.weight = weight
    280         self.LoadResource(gender, path, currentBuildData, forceLoaded=forceLoaded)
    281         return currentBuildData
    282 
    283     @telemetry.ZONE_METHOD
    284     def LoadResource(self, gender, path, modifier, forceLoaded = False):
    285         if not self.resData.QueryPathByGender(gender, path):
    286             log.LogWarn('PaperDoll - Path {0} for gender {1} does not exist that is passed to ModifierLoader::LoadResource!'.format(path, gender))
    287             return
    288         if not forceLoaded:
    289             while not self.IsLoaded:
    290                 pdCF.BeFrameNice()
    291 
    292         resDataEntry = self.resData.GetEntryByPath(gender, path)
    293         modifier.lodCutoff = resDataEntry.lodCutoff or modifier.lodCutoff
    294         self._GetFilesForEntry(resDataEntry, modifier)
    295         parentEntry = self.resData.GetParentEntry(resDataEntry)
    296         if 'colors' in parentEntry.dirs:
    297             colorEntry = self.resData.GetEntryByPath(gender, parentEntry.GetResPath('colors'))
    298             self._PopulateMetaData(modifier, colorEntry)
    299         matchingVariationEntries = (entry for entry in self.resData.GetChildEntries(resDataEntry) if entry.isVariationFolder)
    300         for variationResDataEntry in iter(matchingVariationEntries):
    301             variationModifier = BuildData()
    302             self._GetFilesForEntry(variationResDataEntry, variationModifier)
    303             modifier.variations[variationResDataEntry.leafFolderName] = variationModifier
    304 
    305         if len(modifier.variations):
    306             original = copy.deepcopy(modifier)
    307             original.variations = {}
    308             modifier.variations['v0'] = original
    309         if 'default' in modifier.colorVariations:
    310             modifier.SetColorVariation('default')
    311         if modifier.metaData.alternativeTextureSourcePath:
    312             path = modifier.metaData.alternativeTextureSourcePath.lower()
    313             altEntry = self.resData.GetEntryByPath(gender, path)
    314             self._GetFilesForEntry(altEntry, modifier, sourceMapsOnly=True)
    315 
    316     @telemetry.ZONE_METHOD
    317     def __GetFromYamlCache(self, key):
    318         inst = self.yamlPreloader.LoadYaml(key)
    319         return inst
    320 
    321     @telemetry.ZONE_METHOD
    322     def _PartFromPath(self, path, reverseLookupData = None, rvPart = None):
    323         for part in pdDef.DOLL_PARTS:
    324             if part in path:
    325                 return part
    326 
    327         if '_acc_' in path:
    328             return pdDef.DOLL_PARTS.ACCESSORIES
    329         if reverseLookupData:
    330             for part in iter(reverseLookupData):
    331                 if part in path:
    332                     return rvPart
    333 
    334         return ''
    335 
    336     @telemetry.ZONE_METHOD
    337     def _PopulateSourceMaps(self, modifier, resDataEntry):
    338         files = resDataEntry.sourceMaps.keys()
    339         for f in files:
    340             partType = self._PartFromPath(f) or self._PartFromPath(resDataEntry.respath, pdDef.BODY_CATEGORIES, pdDef.DOLL_PARTS.BODY)
    341             if pdDef.MAP_PREFIX_COLORIZE in f:
    342                 modifier.colorize = True
    343             if pdDef.MAP_SUFFIX_DRGB in f:
    344                 modifier.mapDRGB[partType] = resDataEntry.GetFullResPath(f)
    345             elif pdDef.MAP_SUFFIX_LRGB in f:
    346                 modifier.mapLRGB[partType] = resDataEntry.GetFullResPath(f)
    347             elif pdDef.MAP_SUFFIX_LA in f:
    348                 modifier.mapLA[partType] = resDataEntry.GetFullResPath(f)
    349             elif pdDef.MAP_SUFFIX_DA in f:
    350                 modifier.mapDA[partType] = resDataEntry.GetFullResPath(f)
    351             elif pdDef.MAP_SUFFIX_L in f:
    352                 modifier.mapL[partType] = resDataEntry.GetFullResPath(f)
    353             elif pdDef.MAP_SUFFIX_Z in f:
    354                 modifier.mapZ[partType] = resDataEntry.GetFullResPath(f)
    355             elif pdDef.MAP_SUFFIX_O in f:
    356                 modifier.mapO[partType] = resDataEntry.GetFullResPath(f)
    357             elif pdDef.MAP_SUFFIX_D in f:
    358                 modifier.mapD[partType] = resDataEntry.GetFullResPath(f)
    359             elif pdDef.MAP_SUFFIX_N in f:
    360                 modifier.mapN[partType] = resDataEntry.GetFullResPath(f)
    361             elif pdDef.MAP_SUFFIX_MN in f:
    362                 modifier.mapMN[partType] = resDataEntry.GetFullResPath(f)
    363             elif pdDef.MAP_SUFFIX_TN in f:
    364                 modifier.mapTN[partType] = resDataEntry.GetFullResPath(f)
    365             elif pdDef.MAP_SUFFIX_S in f:
    366                 modifier.mapSRG[partType] = resDataEntry.GetFullResPath(f)
    367             elif pdDef.MAP_SUFFIX_AO in f:
    368                 modifier.mapAO[partType] = resDataEntry.GetFullResPath(f)
    369             elif pdDef.MAP_SUFFIX_MM in f:
    370                 modifier.mapMaterial[partType] = resDataEntry.GetFullResPath(f)
    371             elif pdDef.MAP_SUFFIX_MASK in f or pdDef.MAP_SUFFIX_M in f:
    372                 modifier.mapMask[partType] = resDataEntry.GetFullResPath(f)
    373                 modifier.mapAO[partType] = resDataEntry.GetFullResPath(f)
    374 
    375     @telemetry.ZONE_METHOD
    376     def _PopulateGeometry(self, modifier, resDataEntry):
    377         modifier.clothPath = ''
    378         modifier.clothOverride = ''
    379         modifier.stubblePath = resDataEntry.redFiles.get(pdDef.STUBBLE_PATH, '')
    380         if modifier.stubblePath:
    381             modifier.colorize = True
    382         modifier.shaderPath = resDataEntry.redFiles.get(pdDef.SHADER_PATH, '')
    383         modifier.redfile = resDataEntry.redFiles.get(pdDef.RED_FILE, '')
    384 
    385     @telemetry.ZONE_METHOD
    386     def _PopulateMetaData(self, modifier, resDataEntry):
    387         metaDataFn = 'metadata.yaml'
    388         if metaDataFn in resDataEntry.files:
    389             resPath = resDataEntry.GetFullResPath(metaDataFn)
    390             inst = self.__GetFromYamlCache(resPath)
    391             if inst:
    392                 inst.defaultMetaData = False
    393                 modifier.metaData = inst
    394         files = resDataEntry.GetFilesByExt('pose')
    395         if files:
    396             resPath = resDataEntry.GetFullResPath(files[0])
    397             inst = self.__GetFromYamlCache(resPath)
    398             modifier.poseData = inst
    399         files = resDataEntry.GetFilesByExt('color')
    400         for f in files:
    401             resPath = resDataEntry.GetFullResPath(f)
    402             inst = self.__GetFromYamlCache(resPath)
    403             varName = f.split('.')[0]
    404             modifier.colorVariations[varName] = inst
    405 
    406         files = resDataEntry.GetFilesByExt('proj')
    407         if files:
    408             resPath = resDataEntry.GetFullResPath(files[0])
    409             inst = self.__GetFromYamlCache(resPath)
    410             if inst:
    411                 inst.SetTexturePath(inst.texturePath)
    412                 inst.SetMaskPath(inst.maskPath)
    413                 modifier.decalData = inst
    414 
    415     @telemetry.ZONE_METHOD
    416     def _GetFilesForEntry(self, resDataEntry, modifier, sourceMapsOnly = False):
    417         if resDataEntry:
    418             self._PopulateSourceMaps(modifier, resDataEntry)
    419             dimExt = [ ext for ext in resDataEntry.extToFiles.keys() if ext.isdigit() ]
    420             if dimExt:
    421                 dimFn = resDataEntry.extToFiles[dimExt[0]][0]
    422                 modifier.accessoryMapSize = [ int(x) for x in dimFn.split('.')[-2:] ]
    423             if not sourceMapsOnly:
    424                 self._PopulateGeometry(modifier, resDataEntry)
    425                 self._PopulateMetaData(modifier, resDataEntry)
    426         return modifier
    427 
    428 
    429 class BuildDataMaps(object):
    430 
    431     def __init__(self):
    432         self._isTextureContainingModifier = None
    433         self._contributesToMapTypes = None
    434         self._affectedTextureParts = None
    435         self.mapN = {}
    436         self.mapMN = {}
    437         self.mapTN = {}
    438         self.mapSRG = {}
    439         self.mapAO = {}
    440         self.mapMaterial = {}
    441         self.mapMask = {}
    442         self.mapD = {}
    443         self.mapDRGB = {}
    444         self.mapDA = {}
    445         self.mapLRGB = {}
    446         self.mapLA = {}
    447         self.mapL = {}
    448         self.mapZ = {}
    449         self.mapO = {}
    450 
    451     def IsTextureContainingModifier(self):
    452         if self._isTextureContainingModifier is None:
    453             self._isTextureContainingModifier = False
    454             for each in self.__dict__.iterkeys():
    455                 if each.startswith('map'):
    456                     if self.__dict__[each]:
    457                         self._isTextureContainingModifier = True
    458                         break
    459 
    460         return self._isTextureContainingModifier
    461 
    462     def ContributesToMapTypes(self):
    463         if self._contributesToMapTypes is None:
    464             mapTypes = list()
    465             if len(self.mapD.keys()) > 0 or len(self.mapL.keys()) > 0 or len(self.mapZ.keys()) > 0 or len(self.mapO.keys()) > 0:
    466                 mapTypes.append(pdDef.DIFFUSE_MAP)
    467             if len(self.mapSRG.keys()) > 0 or len(self.mapAO.keys()) > 0 or len(self.mapMaterial.keys()) > 0:
    468                 mapTypes.append(pdDef.SPECULAR_MAP)
    469             if len(self.mapN.keys()) > 0 or len(self.mapMN.keys()) > 0 or len(self.mapTN.keys()) > 0:
    470                 mapTypes.append(pdDef.NORMAL_MAP)
    471             if len(self.mapMask.keys()) > 0:
    472                 mapTypes.append(pdDef.MASK_MAP)
    473             self._contributesToMapTypes = mapTypes
    474         return self._contributesToMapTypes
    475 
    476     def GetAffectedTextureParts(self):
    477         if self._affectedTextureParts is None:
    478             parts = set()
    479             for each in self.__dict__.iterkeys():
    480                 if each.startswith('map'):
    481                     parts.update(self.__dict__[each].keys())
    482 
    483             self._affectedTextureParts = parts
    484         return self._affectedTextureParts
    485 
    486 
    487 class BuildData(BuildDataMaps):
    488     __metaclass__ = telemetry.ZONE_PER_METHOD
    489     DEFAULT_COLORIZEDATA = [pdDef.MID_GRAY] * 3
    490     DEFAULT_PATTERNDATA = [pdDef.DARK_GRAY,
    491      pdDef.LIGHT_GRAY,
    492      pdDef.MID_GRAY,
    493      pdDef.MID_GRAY,
    494      pdDef.MID_GRAY,
    495      (0, 0, 8, 8),
    496      0.0]
    497     DEFAULT_SPECULARCOLORDATA = [pdDef.MID_GRAY] * 3
    498 
    499     def __del__(self):
    500         self.ClearCachedData()
    501 
    502     def __init__(self, pathName = None, name = None, categorie = None):
    503         BuildDataMaps.__init__(self)
    504         self.name = ''
    505         self.categorie = categorie.lower() if categorie else ''
    506         extPath = None
    507         splits = None
    508         if pathName is not None:
    509             pathName = pathName.lower()
    510             splits = pathName.split(pdDef.SEPERATOR_CHAR)
    511             extPath = str(pdDef.SEPERATOR_CHAR.join(splits[1:]))
    512             self.categorie = str(splits[0])
    513         self.lodCutoff = pdDef.LOD_99
    514         self.lodCutin = -pdDef.LOD_99
    515         self.name = name.lower() if name else extPath
    516         if self.categorie and self.name:
    517             self.respath = '{0}/{1}'.format(self.categorie, self.name)
    518         else:
    519             self.respath = ''
    520         if splits and len(splits) > 2:
    521             self.group = splits[1]
    522         else:
    523             self.group = ''
    524         self.usingMaskedShader = False
    525         self.redfile = ''
    526         self.clothPath = ''
    527         self.clothOverride = ''
    528         self.meshes = []
    529         self.__cmpMeshes = []
    530         self.meshGeometryResPaths = {}
    531         self.dependantModifiers = {}
    532         self.__clothData = None
    533         self.__clothDirty = False
    534         self.stubblePath = ''
    535         self.shaderPath = ''
    536         self.drapePath = ''
    537         self.accessoryMapSize = None
    538         self.decalData = None
    539         self.colorize = False
    540         self._colorizeData = list(BuildData.DEFAULT_COLORIZEDATA)
    541         self.__cmpColorizeData = []
    542         self.__pattern = ''
    543         self.patternData = list(BuildData.DEFAULT_PATTERNDATA)
    544         self.__cmpPatternData = []
    545         self.specularColorData = list(BuildData.DEFAULT_SPECULARCOLORDATA)
    546         self.__cmpSpecularColorData = []
    547         self.colorVariations = {}
    548         self.currentColorVariation = ''
    549         self.variations = {}
    550         self.variationTextureHash = ''
    551         self.currentVariation = ''
    552         self.lastVariation = ''
    553         self.__weight = 1.0
    554         self.hasWeightPulse = False
    555         self.__useSkin = False
    556         self.metaData = AvatarPartMetaData()
    557         self.poseData = None
    558         self.__tuck = True
    559         self.ulUVs = (pdDef.DEFAULT_UVS[0], pdDef.DEFAULT_UVS[1])
    560         self.lrUVs = (pdDef.DEFAULT_UVS[2], pdDef.DEFAULT_UVS[3])
    561         self.__IsHidden = False
    562         self.WasHidden = False
    563         self.__IsDirty = True
    564         self.__hashValue = None
    565 
    566     def HasWeightPulse(self):
    567         return self.hasWeightPulse
    568 
    569     def IsVisibleAtLOD(self, lod):
    570         return self.lodCutin <= lod <= self.lodCutoff
    571 
    572     def GetUVsForCompositing(self, bodyPart):
    573         if bodyPart != pdDef.DOLL_PARTS.ACCESSORIES:
    574             if bodyPart == pdDef.DOLL_PARTS.BODY:
    575                 UVs = pdDef.BODY_UVS
    576             elif bodyPart == pdDef.DOLL_PARTS.HEAD:
    577                 UVs = pdDef.HEAD_UVS
    578             elif bodyPart == pdDef.DOLL_PARTS.HAIR:
    579                 UVs = pdDef.HAIR_UVS
    580         else:
    581             accUVs = pdDef.ACCE_UVS
    582             width = accUVs[2] - accUVs[0]
    583             height = accUVs[3] - accUVs[1]
    584             UVs = list((accUVs[0] + self.ulUVs[0] * width, accUVs[1] + self.ulUVs[1] * height) + (accUVs[0] + self.lrUVs[0] * width, accUVs[1] + self.lrUVs[1] * height))
    585         return UVs
    586 
    587     def SetColorizeData(self, *args):
    588         x = args
    589         depth = 0
    590         while len(x) == 1 and type(x[0]) in (tuple, list) and depth < 5:
    591             x = x[0]
    592             depth += 1
    593 
    594         didChange = False
    595         for i in xrange(len(x)):
    596             if len(x) > i and type(x[i]) in (tuple, list):
    597                 if self._colorizeData[i] != tuple(x[i]):
    598                     self._colorizeData[i] = tuple(x[i])
    599                     didChange = True
    600 
    601         if didChange:
    602             self.IsDirty = True
    603 
    604     def GetColorizeData(self):
    605         return self._colorizeData
    606 
    607     def GetColorVariations(self):
    608         return self.colorVariations.keys()
    609 
    610     def SetColorVariation(self, variationName):
    611         if variationName == 'none':
    612             self.currentColorVariation = 'none'
    613             return
    614         if not self.currentColorVariation == 'none' and self.colorVariations and variationName in self.colorVariations:
    615             currentColorVariation = self.colorVariations[variationName]
    616             if not currentColorVariation:
    617                 return
    618             if 'colors' in currentColorVariation:
    619                 for i in xrange(3):
    620                     self.colorizeData[i] = currentColorVariation['colors'][i]
    621 
    622             if 'pattern' in currentColorVariation:
    623                 self.pattern = currentColorVariation['pattern']
    624             if 'patternColors' in currentColorVariation:
    625                 arrayLength = len(currentColorVariation['patternColors'])
    626                 for i in xrange(arrayLength):
    627                     self.patternData[i] = currentColorVariation['patternColors'][i]
    628 
    629             if 'specularColors' in currentColorVariation:
    630                 for i in xrange(3):
    631                     self.specularColorData[i] = currentColorVariation['specularColors'][i]
    632 
    633             self.currentColorVariation = str(variationName)
    634 
    635     def SetColorVariationDirectly(self, variation):
    636         if variation is not None and type(variation) is types.DictionaryType:
    637             if 'colors' in variation:
    638                 for i in xrange(3):
    639                     self.colorizeData[i] = variation['colors'][i]
    640 
    641             if 'pattern' in variation:
    642                 self.pattern = variation['pattern']
    643             if 'patternColors' in variation:
    644                 for i in xrange(5):
    645                     self.patternData[i] = variation['patternColors'][i]
    646 
    647             if 'specularColors' in variation:
    648                 for i in xrange(3):
    649                     self.specularColorData[i] = variation['specularColors'][i]
    650 
    651     def SetColorVariationSpecularity(self, specularColor):
    652         self.specularColorData = specularColor
    653 
    654     def GetColorsFromColorVariation(self, variationName):
    655         if self.colorVariations and self.colorVariations.get(variationName):
    656             var = self.colorVariations[variationName]
    657             if var['pattern'] != '':
    658                 return [var['patternColors'][0], var['patternColors'][3], var['patternColors'][4]]
    659             else:
    660                 return var['colors']
    661 
    662     def GetVariations(self):
    663         return self.variations.keys()
    664 
    665     def SetVariation(self, variationName):
    666         variationName = variationName or 'v0'
    667         if self.variations and variationName in self.variations:
    668             oldRedFile = self.redfile
    669             oldClothPath = self.clothPath
    670             var = self.variations[variationName]
    671             doNotCopy = ['respath', 'dependantModifiers']
    672             for member in var.__dict__:
    673                 if member not in doNotCopy:
    674                     if type(var.__dict__[member]) == str and var.__dict__[member]:
    675                         self.__dict__[member] = var.__dict__[member]
    676                     if type(var.__dict__[member]) == dict and len(var.__dict__[member]) > 0:
    677                         for entry in var.__dict__[member]:
    678                             self.__dict__[member][entry] = var.__dict__[member][entry]
    679                             if member.startswith('map'):
    680                                 self.variationTextureHash = var.__dict__[member][entry]
    681 
    682                     if member == 'metaData':
    683                         if var.metaData and not var.metaData.defaultMetaData:
    684                             self.metaData = var.metaData
    685 
    686             if oldRedFile != self.redfile:
    687                 del self.meshes[:]
    688                 self.meshGeometryResPaths = {}
    689             if oldClothPath != self.clothPath:
    690                 self.clothData = None
    691             self.lastVariation = self.currentVariation
    692             self.currentVariation = str(variationName)
    693 
    694     def GetVariationMetaData(self, variationName = None):
    695         if variationName == '':
    696             variationName = 'v0'
    697         variationName = variationName or self.currentVariation
    698         variation = self.variations.get(variationName)
    699         if variation and variation.metaData and not variation.metaData.defaultMetaData:
    700             return variation.metaData
    701 
    702     def GetDependantModifiersFullData(self, metaDataOverride = None):
    703         metaData = metaDataOverride or self.metaData
    704         if metaData.dependantModifiers:
    705             parsedValues = []
    706             for each in metaData.dependantModifiers:
    707                 if '#' in each:
    708                     tmpList = []
    709                     for elem in each.split('#'):
    710                         tmpList.append(elem)
    711 
    712                     while len(tmpList) < 3:
    713                         tmpList.append('')
    714 
    715                     if len(tmpList) < 4:
    716                         tmpList.append(1.0)
    717                     else:
    718                         tmpList[3] = float(tmpList[3])
    719                     parsedValues.append(tuple(tmpList))
    720                 else:
    721                     parsedValues.append((each,
    722                      '',
    723                      '',
    724                      1.0))
    725 
    726             return parsedValues
    727 
    728     def GetDependantModifierResPaths(self):
    729         if self.metaData.dependantModifiers:
    730             resPaths = []
    731             for resPath in self.metaData.dependantModifiers:
    732                 if '#' in resPath:
    733                     resPaths.append(resPath.split('#')[0])
    734                 else:
    735                     resPaths.append(resPath)
    736 
    737             return resPaths
    738 
    739     def GetOccludedModifiersFullData(self, metaDataOverride = None):
    740         if metaDataOverride:
    741             metaData = metaDataOverride
    742         else:
    743             metaData = self.metaData
    744         if metaData.occludesModifiers:
    745             parsedValues = []
    746             for each in metaData.occludesModifiers:
    747                 if '#' in each:
    748                     tmpList = []
    749                     for elem in each.split('#'):
    750                         tmpList.append(elem)
    751 
    752                     if len(tmpList) < 2:
    753                         tmpList.append(1.0)
    754                     else:
    755                         tmpList[1] = float(tmpList[1])
    756                     tmpList[0] = tmpList[0].lower()
    757                     parsedValues.append(tuple(tmpList))
    758                 else:
    759                     parsedValues.append((each.lower(), 1.0))
    760 
    761             return parsedValues
    762 
    763     def GetDependantModifiers(self):
    764         return self.dependantModifiers.values()
    765 
    766     def AddDependantModifier(self, modifier):
    767         if self.respath == modifier.respath:
    768             raise AttributeError('paperDoll:BuildData:AddDependantModifier - Trying to add modifier as dependant of itself!')
    769         self.dependantModifiers[modifier.respath] = modifier
    770 
    771     def RemoveDependantModifier(self, modifier):
    772         if modifier.respath in self.dependantModifiers:
    773             del self.dependantModifiers[modifier.respath]
    774 
    775     def GetMeshSourcePaths(self):
    776         return [self.clothPath, self.redfile]
    777 
    778     def IsMeshDirty(self):
    779         return self.__clothDirty or self.clothPath and not self.clothData or self.__cmpMeshes != self.meshes or any(map(lambda mesh: not (mesh.geometry and mesh.geometry.isGood), self.meshes))
    780 
    781     def IsMeshContainingModifier(self):
    782         return any((self.meshes,
    783          self.clothData,
    784          self.clothPath,
    785          self.redfile))
    786 
    787     def IsBlendshapeModifier(self):
    788         return self.categorie in pdDef.BLENDSHAPE_CATEGORIES
    789 
    790     def __repr__(self):
    791         s = 'BuildData instance, ID%s\n' % id(self)
    792         s = s + 'Name: [%s]\t Category: [%s]\t RedFile: [%s]\n' % (self.name, self.categorie, self.redfile)
    793         s = s + 'Dirty: [%s]\t Hidden: [%s]\t Respath: [%s]\t' % (self.IsDirty, self.IsHidden, self.GetResPath())
    794         return s
    795 
    796     def __hash__(self):
    797         return id(self)
    798 
    799     def getIsDirty(self):
    800         if self.__IsDirty:
    801             return True
    802         if self.lastVariation != self.currentVariation:
    803             return True
    804         if self.__cmpPatternData != self.patternData:
    805             return True
    806         if self.__cmpColorizeData != self._colorizeData:
    807             return True
    808         if self.__cmpSpecularColorData != self.specularColorData:
    809             return True
    810         if self.__cmpMeshes != self.meshes:
    811             return True
    812         if self.__cmpDecalData != self.decalData:
    813             return True
    814         return False
    815 
    816     def setIsDirty(self, value):
    817         if value == False:
    818             self.__cmpColorizeData = list(self._colorizeData)
    819             self.__cmpSpecularColorData = list(self.specularColorData)
    820             self.__cmpPatternData = list(self.patternData)
    821             self.__cmpMeshes = list(self.meshes)
    822             if self.decalData is not None:
    823                 self.__cmpDecalData = copy.deepcopy(self.decalData)
    824             else:
    825                 self.__cmpDecalData = None
    826             self.lastVariation = self.currentVariation
    827             self.WasHidden = False
    828             self.WasShown = False
    829             self.__clothDirty = False
    830         else:
    831             self.__hashValue = None
    832             self._isTextureContainingModifier = None
    833             self._contributesToMapTypes = None
    834             self._affectedTextureParts = None
    835         self.__IsDirty = value
    836 
    837     IsDirty = property(fget=getIsDirty, fset=setIsDirty)
    838 
    839     def dirtDeco(fun):
    840 
    841         def new(*args):
    842             args[0].__IsDirty = True
    843             return fun(*args)
    844 
    845         return new
    846 
    847     colorizeData = property(fset=SetColorizeData, fget=GetColorizeData)
    848 
    849     @dirtDeco
    850     def setisHidden(self, value):
    851         self.WasShown = value and not self.__IsHidden
    852         self.WasHidden = not value and self.__IsHidden
    853         self.__IsHidden = value
    854 
    855     IsHidden = property(fget=lambda self: self.__IsHidden, fset=setisHidden)
    856 
    857     @property
    858     def clothData(self):
    859         return self.__clothData
    860 
    861     @clothData.setter
    862     def clothData(self, value):
    863         self.__clothDirty = True
    864         self.__clothData = value
    865 
    866     @dirtDeco
    867     def settuck(self, value):
    868         self.__tuck = value
    869 
    870     tuck = property(fget=lambda self: self.__tuck, fset=settuck)
    871 
    872     @dirtDeco
    873     def setpattern(self, value):
    874         self.__pattern = value
    875 
    876     pattern = property(fget=lambda self: self.__pattern, fset=setpattern)
    877 
    878     def setweight(self, value):
    879         if self.__weight == value:
    880             return
    881         if value > 0 and self.__weight <= 0:
    882             self.hasWeightPulse = True
    883         elif self.__weight > 0 and value <= 0:
    884             self.hasWeightPulse = True
    885         self.__weight = value
    886         self.__IsDirty = True
    887 
    888     weight = property(fget=lambda self: self.__weight, fset=setweight)
    889 
    890     @dirtDeco
    891     def setuseSkin(self, value):
    892         self.__useSkin = value
    893 
    894     useSkin = property(fget=lambda self: self.__useSkin, fset=setuseSkin)
    895 
    896     def GetTypeData(self):
    897         return (self.respath, self.currentVariation, self.currentColorVariation)
    898 
    899     def ClearCachedData(self):
    900         del self.meshes[:]
    901         del self.__cmpMeshes[:]
    902         self.clothData = None
    903         self.meshGeometryResPaths = {}
    904 
    905     def GetResPath(self):
    906         return self.respath
    907 
    908     def GetFootPrint(self, preserveTypes = False, occlusionWeight = None):
    909         colorsOutput = self.colorizeData if preserveTypes else str(self.colorizeData)
    910         colorsSource = self.colorizeData
    911         if self.pattern:
    912             colorsOutput = self.patternData if preserveTypes else str(self.patternData)
    913             colorsSource = self.patternData
    914         data = {}
    915         data[pdDef.DNA_STRINGS.PATH] = self.GetResPath()
    916         serializationWeight = self.weight if not occlusionWeight else self.weight + occlusionWeight
    917         data[pdDef.DNA_STRINGS.WEIGHT] = serializationWeight
    918         data[pdDef.DNA_STRINGS.CATEGORY] = self.categorie
    919         if colorsSource != BuildData.DEFAULT_COLORIZEDATA:
    920             data[pdDef.DNA_STRINGS.COLORS] = colorsOutput
    921         if self.specularColorData != BuildData.DEFAULT_SPECULARCOLORDATA:
    922             data[pdDef.DNA_STRINGS.SPECULARCOLORS] = self.specularColorData
    923         if self.pattern:
    924             data[pdDef.DNA_STRINGS.PATTERN] = self.pattern
    925         if self.decalData:
    926             data[pdDef.DNA_STRINGS.DECALDATA] = self.decalData
    927         if self.currentColorVariation:
    928             data[pdDef.DNA_STRINGS.COLORVARIATION] = self.currentColorVariation
    929         if self.currentVariation:
    930             data[pdDef.DNA_STRINGS.VARIATION] = self.currentVariation
    931         return data
    932 
    933     def CompareFootPrint(self, other):
    934         if isinstance(other, BuildData):
    935             otherFP = other.GetFootPrint()
    936         else:
    937             otherFP = other
    938 
    939         def doCompare(sfp, ofp):
    940             ret = 1
    941             for k, v in sfp.iteritems():
    942                 if ofp.get(k) != v:
    943                     if k != pdDef.DNA_STRINGS.VARIATION:
    944                         return 0
    945                     ret = -1
    946 
    947             return ret
    948 
    949         selfFP = self.GetFootPrint(preserveTypes=True)
    950         cmpResult = doCompare(selfFP, otherFP)
    951         if cmpResult < 1:
    952             selfFP = self.GetFootPrint(preserveTypes=False)
    953             cmpResult = doCompare(selfFP, otherFP)
    954         return cmpResult
    955 
    956     def Hash(self):
    957         return hash(self)
    958 
    959 
    960 class BuildDataManager(object):
    961     __metaclass__ = telemetry.ZONE_PER_METHOD
    962 
    963     def getmodifiers(self):
    964         if self.__filterHidden:
    965             modifiersdata = {}
    966             for part in self.__modifiers.iterkeys():
    967                 modifiers = [ modifier for modifier in self.__modifiers[part] if not modifier._BuildData__IsHidden ]
    968                 modifiersdata[part] = modifiers
    969 
    970             return modifiersdata
    971         else:
    972             return self.__modifiers
    973 
    974     def setmodifiers(self, value):
    975         raise AttributeError('Cannot directly set modifiers!')
    976 
    977     modifiersdata = property(fget=getmodifiers, fset=setmodifiers)
    978 
    979     def __del__(self):
    980         del self.__sortedList
    981         del self.__dirtyModifiersAdded
    982         del self.__dirtyModifiersRemoved
    983         del self.__modifiers
    984 
    985     def __init__(self):
    986         object.__init__(self)
    987         self.__modifiers = {pdDef.DOLL_PARTS.BODY: [],
    988          pdDef.DOLL_PARTS.HEAD: [],
    989          pdDef.DOLL_PARTS.HAIR: [],
    990          pdDef.DOLL_PARTS.ACCESSORIES: [],
    991          pdDef.DOLL_EXTRA_PARTS.BODYSHAPES: [],
    992          pdDef.DOLL_EXTRA_PARTS.UTILITYSHAPES: [],
    993          pdDef.DOLL_EXTRA_PARTS.UNDEFINED: []}
    994         self.desiredOrder = pdDef.DESIRED_ORDER[:]
    995         self.desiredOrderChanged = False
    996         self.__sortedList = []
    997         self.__dirty = False
    998         self.modifierLimits = {}
    999         self.occludeRules = {}
   1000         self.__dirtyModifiersAdded = []
   1001         self.__dirtyModifiersRemoved = []
   1002         self.currentLOD = pdDef.LOD_0
   1003         self.__locked = False
   1004         self.__pendingModifiersToAdd = []
   1005         self.__pendingModifiersToRemove = []
   1006         self.__filterHidden = True
   1007         self._parentModifiers = {}
   1008 
   1009     def AddBlendshapeLimitsFromFile(self, resPath):
   1010         data = ModifierLoader.LoadBlendshapeLimits(resPath)
   1011         if data:
   1012             limits = data['limits']
   1013             self.AddModifierLimits(limits)
   1014 
   1015     def AddModifierLimits(self, modifierLimits):
   1016         self.modifierLimits.update(modifierLimits)
   1017 
   1018     def ApplyLimitsToModifierWeights(self):
   1019         for modifier in self.GetModifiersAsList(includeFuture=True, showHidden=True):
   1020             limit = self.modifierLimits.get(modifier.name)
   1021             if limit:
   1022                 minLimit, maxLimit = limit
   1023                 modifier.weight = min(max(minLimit, modifier.weight), maxLimit)
   1024 
   1025     def GetMorphTargets(self):
   1026         modifierList = self.GetModifiersAsList()
   1027         removedModifiers = list(self.__dirtyModifiersRemoved)
   1028         morphTargets = {}
   1029 
   1030         def Qualifier(modifier):
   1031             return modifier.IsBlendshapeModifier()
   1032 
   1033         for modifier in iter(removedModifiers):
   1034             if Qualifier(modifier):
   1035                 morphTargets[modifier.name] = 0
   1036 
   1037         for modifier in iter(modifierList):
   1038             if Qualifier(modifier):
   1039                 weight = modifier.weight if modifier.weight < 1.0 else 1.0
   1040                 limit = self.modifierLimits.get(modifier.name)
   1041                 if limit:
   1042                     minLimit, maxLimit = limit
   1043                     weight = min(max(minLimit, weight), maxLimit)
   1044                 morphTargets[modifier.name] = weight
   1045 
   1046         return morphTargets
   1047 
   1048     def AddParentModifier(self, modifier, parentModifier):
   1049         parentModifiers = self.GetParentModifiers(modifier)
   1050         if parentModifier not in parentModifiers:
   1051             parentModifiers.append(parentModifier)
   1052         self._parentModifiers[modifier] = parentModifiers
   1053 
   1054     def GetParentModifiers(self, modifier):
   1055         return self._parentModifiers.get(modifier, [])
   1056 
   1057     def RemoveParentModifier(self, modifier, dependantModifier = None):
   1058         if dependantModifier:
   1059             parentModifiers = self.GetParentModifiers(dependantModifier)
   1060             if modifier in parentModifiers:
   1061                 parentModifiers.remove(modifier)
   1062         else:
   1063             for parentModifiers in self._parentModifiers.itervalues():
   1064                 if modifier in parentModifiers:
   1065                     parentModifiers.remove(modifier)
   1066 
   1067     def GetSoundTags(self):
   1068         soundTags = []
   1069         for modifier in iter(self.GetSortedModifiers()):
   1070             soundTag = modifier.metaData.soundTag
   1071             if soundTag and soundTag not in soundTags:
   1072                 soundTags.append(soundTag)
   1073 
   1074         return soundTags
   1075 
   1076     def Lock(self):
   1077         self.__locked = True
   1078 
   1079     def UnLock(self):
   1080         self.__locked = False
   1081         for modifier in self.__pendingModifiersToRemove:
   1082             self.RemoveModifier(modifier)
   1083 
   1084         self.__pendingModifiersToRemove = []
   1085         for modifier in self.__pendingModifiersToAdd:
   1086             self.AddModifier(modifier)
   1087 
   1088         self.__pendingModifiersToAdd = []
   1089 
   1090     def HashForMaps(self, hashableElements = None):
   1091         hasher = hashlib.md5()
   1092         if hashableElements:
   1093             for he in iter(hashableElements):
   1094                 hasher.update(str(he))
   1095 
   1096         for part, modifiers in self.modifiersdata.iteritems():
   1097             hasher.update(part)
   1098             for modifier in iter(modifiers):
   1099                 if not (modifier.IsTextureContainingModifier() or modifier.metaData.hidesBootShin or modifier.metaData.forcesLooseTop or modifier.metaData.swapTops or modifier.metaData.swapBottom or modifier.metaData.swapSocks):
   1100                     continue
   1101                 modString = '{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}'.format(modifier.name, modifier.categorie, modifier.weight, modifier.colorizeData, modifier.specularColorData, modifier.pattern, modifier.patternData, modifier.decalData, modifier.useSkin, modifier.variationTextureHash)
   1102                 if modifier.metaData.hidesBootShin or modifier.metaData.forcesLooseTop or modifier.metaData.swapTops or modifier.metaData.swapBottom or modifier.metaData.swapSocks:
   1103                     modString = '{0}{1}{2}'.format(modString, modifier.metaData.hidesBootShin, modifier.metaData.forcesLooseTop, modifier.metaData.swapTops, modifier.metaData.swapBottom, modifier.metaData.swapSocks)
   1104                 hasher.update(modString)
   1105 
   1106             pdCF.BeFrameNice()
   1107 
   1108         return hasher.hexdigest()
   1109 
   1110     def GetDirtyModifiers(self, changedBit = False, addedBit = False, removedBit = False, getWeightless = True):
   1111         ret = list()
   1112         masking = changedBit or addedBit or removedBit
   1113         if addedBit or not masking:
   1114             if getWeightless:
   1115                 ret.extend(self.__dirtyModifiersAdded)
   1116             else:
   1117                 ret.extend((modifier for modifier in self.__dirtyModifiersAdded if modifier.weight > 0))
   1118         if removedBit or not masking:
   1119             if getWeightless:
   1120                 ret.extend(self.__dirtyModifiersRemoved)
   1121             else:
   1122                 ret.extend((modifier for modifier in self.__dirtyModifiersRemoved if modifier.weight > 0))
   1123         if changedBit or not masking:
   1124             self.__filterHidden = False
   1125             changedModifiers = []
   1126             for modifiers in self.modifiersdata.itervalues():
   1127                 if getWeightless:
   1128                     changedModifiers.extend((modifier for modifier in modifiers if modifier.IsDirty and modifier not in self.__dirtyModifiersAdded))
   1129                 else:
   1130                     changedModifiers.extend((modifier for modifier in modifiers if modifier.IsDirty and modifier.weight > 0 and modifier not in self.__dirtyModifiersAdded))
   1131 
   1132             self.__filterHidden = True
   1133             for modifier in changedModifiers:
   1134                 if modifier not in ret:
   1135                     ret.append(modifier)
   1136 
   1137         ret = self.SortParts(ret)
   1138         return ret
   1139 
   1140     @telemetry.ZONE_METHOD
   1141     def NotifyUpdate(self):
   1142         del self.__dirtyModifiersAdded[:]
   1143         for modifier in iter(self.__dirtyModifiersRemoved):
   1144             modifier.ClearCachedData()
   1145 
   1146         del self.__dirtyModifiersRemoved[:]
   1147         for modifier in iter(self.GetModifiersAsList(showHidden=True)):
   1148             modifier.IsDirty = False
   1149 
   1150         self.desiredOrderChanged = False
   1151         self.__dirty = False
   1152 
   1153     def SetAllAsDirty(self, clearMeshes = False):
   1154         for part in self.modifiersdata.iterkeys():
   1155             for modifier in self.modifiersdata[part]:
   1156                 modifier.IsDirty = True
   1157                 if clearMeshes:
   1158                     modifier.ClearCachedData()
   1159 
   1160         self.__dirty = True
   1161 
   1162     def SetLOD(self, newLod):
   1163         self.currentLOD = newLod
   1164         modifiers = self.GetModifiersAsList(showHidden=True)
   1165         for modifier in modifiers:
   1166             if not modifier.IsVisibleAtLOD(self.currentLOD):
   1167                 self.HideModifier(modifier)
   1168             elif modifier.IsHidden:
   1169                 self.ShowModifier(modifier)
   1170 
   1171     def RemoveMeshContainingModifiers(self, category, privilegedCaller = False):
   1172         for modifier in self.GetModifiersByCategory(category):
   1173             if modifier.IsMeshContainingModifier() and not self.GetParentModifiers(modifier):
   1174                 self.RemoveModifier(modifier, privilegedCaller=privilegedCaller)
   1175 
   1176     def AddModifier(self, modifier, privilegedCaller = False):
   1177         if self.__locked and not privilegedCaller:
   1178             self.__pendingModifiersToAdd.append(modifier)
   1179         else:
   1180             part = self.CategoryToPart(modifier.categorie)
   1181             for existingModifier in iter(self.__modifiers[part]):
   1182                 if existingModifier.respath == modifier.respath:
   1183                     if existingModifier.IsVisibleAtLOD(self.currentLOD):
   1184                         if existingModifier.IsHidden:
   1185                             self.ShowModifier(existingModifier)
   1186                     else:
   1187                         self.HideModifier(existingModifier)
   1188                     if modifier.weight > existingModifier.weight:
   1189                         existingModifier.weight = modifier.weight
   1190                         self.ApplyOccludeRules(existingModifier)
   1191                     return
   1192 
   1193             if not modifier.IsVisibleAtLOD(self.currentLOD):
   1194                 self.HideModifier(modifier)
   1195             self.ApplyOccludeRules(modifier)
   1196             if modifier.weight > 0:
   1197                 if modifier.categorie == pdDef.BODY_CATEGORIES.TOPOUTER:
   1198                     self.RemoveMeshContainingModifiers(pdDef.BODY_CATEGORIES.TOPINNER, privilegedCaller=privilegedCaller)
   1199                 elif modifier.categorie == pdDef.BODY_CATEGORIES.BOTTOMOUTER:
   1200                     self.RemoveMeshContainingModifiers(pdDef.BODY_CATEGORIES.BOTTOMINNER, privilegedCaller=privilegedCaller)
   1201                 if modifier.IsMeshContainingModifier() and modifier.categorie not in (pdDef.DOLL_PARTS.ACCESSORIES, pdDef.DOLL_EXTRA_PARTS.DEPENDANTS):
   1202                     self.RemoveMeshContainingModifiers(modifier.categorie, privilegedCaller=privilegedCaller)
   1203             self.OccludeModifiersByModifier(modifier)
   1204             self.__dirtyModifiersAdded.append(modifier)
   1205             resPaths = modifier.GetDependantModifierResPaths()
   1206             if resPaths:
   1207                 for resPath in iter(resPaths):
   1208                     dependantModifier = self.GetModifierByResPath(resPath)
   1209                     if dependantModifier:
   1210                         modifier.AddDependantModifier(dependantModifier)
   1211                         self.AddParentModifier(dependantModifier, modifier)
   1212 
   1213             for dependantModifier in iter(modifier.GetDependantModifiers()):
   1214                 self.AddModifier(dependantModifier, privilegedCaller=privilegedCaller)
   1215                 self.AddParentModifier(dependantModifier, modifier)
   1216 
   1217             if modifier in self.__dirtyModifiersRemoved:
   1218                 self.__dirtyModifiersRemoved.remove(modifier)
   1219             self.__modifiers[part].append(modifier)
   1220             self.__dirty = True
   1221 
   1222     def OccludeModifiersByModifier(self, modifier):
   1223         occludeData = modifier.GetOccludedModifiersFullData()
   1224         if occludeData:
   1225             for resPath, weightSubtraction in occludeData:
   1226                 self.UpdateOccludeRule(resPath, weightSubtraction)
   1227                 occlusionTargets = []
   1228                 if pdDef.SEPERATOR_CHAR not in resPath:
   1229                     occlusionTargets.extend(self.GetModifiersByCategory(resPath))
   1230                 elif resPath.count(pdDef.SEPERATOR_CHAR) == 1 and resPath.split(pdDef.SEPERATOR_CHAR)[0] in pdDef.CATEGORIES_CONTAINING_GROUPS:
   1231                     category, group = resPath.split(pdDef.SEPERATOR_CHAR)[:2]
   1232                     groupCandidateModifiers = self.GetModifiersByCategory(category)
   1233                     for groupCandidateModifier in groupCandidateModifiers:
   1234                         if group == groupCandidateModifier.group:
   1235                             occlusionTargets.append(groupCandidateModifier)
   1236 
   1237                 else:
   1238                     targetToOcclude = self.GetModifierByResPath(resPath, includeFuture=True)
   1239                     if targetToOcclude:
   1240                         occlusionTargets.append(targetToOcclude)
   1241                 for targetToOcclude in occlusionTargets:
   1242                     targetToOcclude.weight -= weightSubtraction
   1243 
   1244     def GetOcclusionWeight(self, modifier):
   1245         occlusionWeight = 0
   1246         for resPath in self.occludeRules.iterkeys():
   1247             if resPath in modifier.respath:
   1248                 occlusionWeight += self.occludeRules[resPath]
   1249 
   1250         return occlusionWeight
   1251 
   1252     def ApplyOccludeRules(self, modifier):
   1253         modifier.weight -= self.GetOcclusionWeight(modifier)
   1254 
   1255     def IsCategoryOccluded(self, category):
   1256         for resPath, weight in self.occludeRules.iteritems():
   1257             if weight >= 1.0 and resPath == category:
   1258                 return True
   1259 
   1260         return False
   1261 
   1262     def UpdateOccludeRule(self, resPath, weight):
   1263         occludeRule = self.occludeRules.get(resPath, 0)
   1264         occludeRule += weight
   1265         if occludeRule <= 0.0:
   1266             try:
   1267                 del self.occludeRules[resPath]
   1268             except KeyError:
   1269                 pass
   1270 
   1271         else:
   1272             self.occludeRules[resPath] = occludeRule
   1273 
   1274     def ReverseOccludeModifiersByModifier(self, modifier, useVariation = None):
   1275         occludeData = None
   1276         if useVariation is not None:
   1277             metaData = modifier.GetVariationMetaData(useVariation)
   1278             occludeData = modifier.GetOccludedModifiersFullData(metaData)
   1279         else:
   1280             occludeData = modifier.GetOccludedModifiersFullData()
   1281         if occludeData:
   1282             for resPath, weightSubtraction in occludeData:
   1283                 self.UpdateOccludeRule(resPath, -weightSubtraction)
   1284                 modifiersToReverseOcclude = []
   1285                 if pdDef.SEPERATOR_CHAR not in resPath:
   1286                     for occludedModifier in self.GetModifiersByCategory(resPath):
   1287                         modifiersToReverseOcclude.append(occludedModifier)
   1288 
   1289                 elif resPath.count(pdDef.SEPERATOR_CHAR) == 1 and resPath.split(pdDef.SEPERATOR_CHAR)[0] in pdDef.CATEGORIES_CONTAINING_GROUPS:
   1290                     category, group = resPath.split(pdDef.SEPERATOR_CHAR)[:2]
   1291                     groupCandidateModifiers = self.GetModifiersByCategory(category)
   1292                     for groupCandidateModifier in groupCandidateModifiers:
   1293                         if group == groupCandidateModifier.group:
   1294                             modifiersToReverseOcclude.append(groupCandidateModifier)
   1295 
   1296                 else:
   1297                     occludedModifier = self.GetModifierByResPath(resPath)
   1298                     if occludedModifier:
   1299                         modifiersToReverseOcclude.append(occludedModifier)
   1300                 for target in modifiersToReverseOcclude:
   1301                     oldWeight = target.weight
   1302                     target.weight += weightSubtraction
   1303                     if oldWeight <= 0 and target.weight > 0:
   1304                         self.AddModifier(target, privilegedCaller=True)
   1305 
   1306     def RemoveModifier(self, modifier, privilegedCaller = False, occludingCall = False):
   1307         if self.__locked and not privilegedCaller:
   1308             self.__pendingModifiersToRemove.append(modifier)
   1309         else:
   1310             if self.GetParentModifiers(modifier) and not occludingCall:
   1311                 log.LogWarn('paperDoll::BuildDataManager::RemoveModifier - Attempting to remove a modifier that has parent modifiers', modifier)
   1312                 return
   1313             if modifier in self.__dirtyModifiersRemoved:
   1314                 return
   1315             part = self.CategoryToPart(modifier.categorie)
   1316             if modifier in self.__modifiers[part]:
   1317                 self.__modifiers[part].remove(modifier)
   1318             if modifier in self.__dirtyModifiersAdded:
   1319                 self.__dirtyModifiersAdded.remove(modifier)
   1320             self.__dirtyModifiersRemoved.append(modifier)
   1321             replacementPaths = (modifier.metaData.lod1Replacement, modifier.metaData.lod2Replacement)
   1322             for replacementPath in replacementPaths:
   1323                 if replacementPath:
   1324                     lodReplacementModifier = self.GetModifierByResPath(replacementPath)
   1325                     if lodReplacementModifier:
   1326                         self.RemoveModifier(lodReplacementModifier)
   1327 
   1328             self.ReverseOccludeModifiersByModifier(modifier)
   1329             self.RemoveParentModifier(modifier)
   1330             for dependantModifier in iter(modifier.GetDependantModifiers()):
   1331                 parentModifiers = self.GetParentModifiers(dependantModifier)
   1332                 if parentModifiers:
   1333                     dependantModifier.weight = 0
   1334                     for modifier in iter(parentModifiers):
   1335                         for entry in modifier.GetDependantModifiersFullData():
   1336                             if entry[0] == dependantModifier.respath and entry[3] > dependantModifier.weight:
   1337                                 dependantModifier.weight = entry[3]
   1338 
   1339                     self.ApplyOccludeRules(dependantModifier)
   1340                 if occludingCall or len(parentModifiers) == 0:
   1341                     self.RemoveModifier(dependantModifier, privilegedCaller=privilegedCaller)
   1342 
   1343             self.__dirty = True
   1344 
   1345     def SetModifiersByCategory(self, category, modifiers, privilegedCaller = False):
   1346         self.__dirty = True
   1347         if type(modifiers) == BuildData:
   1348             modifiers = [modifiers]
   1349         removeModifiers = self.GetModifiersByCategory(category)
   1350         for modifier in iter(removeModifiers):
   1351             self.RemoveModifier(modifier, privilegedCaller=privilegedCaller)
   1352 
   1353         for modifier in iter(modifiers):
   1354             self.AddModifier(modifier, privilegedCaller=privilegedCaller)
   1355 
   1356     def GetModifiersByCategory(self, category, showHidden = False, includeFuture = False):
   1357         filterHiddenState = self.__filterHidden
   1358         if showHidden:
   1359             self.__filterHidden = False
   1360         part = self.CategoryToPart(category)
   1361         modifiers = self.modifiersdata.get(part, [])
   1362         modifiers = [ modifier for modifier in modifiers if modifier.categorie == category ]
   1363         if includeFuture:
   1364             for modifier in self.__pendingModifiersToAdd:
   1365                 if modifier.categorie == category:
   1366                     modifiers.insert(0, modifier)
   1367 
   1368         self.__filterHidden = filterHiddenState
   1369         return modifiers
   1370 
   1371     def GetModifiersByPart(self, part, showHidden = False):
   1372         filterHiddenState = self.__filterHidden
   1373         if showHidden:
   1374             self.__filterHidden = False
   1375         modifiers = self.modifiersdata.get(part, [])
   1376         self.__filterHidden = filterHiddenState
   1377         return modifiers
   1378 
   1379     def GetModifierByResPath(self, resPath, includeFuture = False, showHidden = False):
   1380         for modifier in self.GetModifiersAsList(includeFuture=includeFuture, showHidden=showHidden):
   1381             if modifier.respath == resPath:
   1382                 return modifier
   1383 
   1384     def GetMeshSourcePaths(self, modifiers = None):
   1385         meshSourcePaths = list()
   1386         if modifiers is None:
   1387             modifiers = self.GetSortedModifiers()
   1388         for modifier in iter(modifiers):
   1389             meshSourcePaths.extend(modifier.GetMeshSourcePaths())
   1390 
   1391         while None in meshSourcePaths:
   1392             meshSourcePaths.remove(None)
   1393 
   1394         while '' in meshSourcePaths:
   1395             meshSourcePaths.remove('')
   1396 
   1397         return meshSourcePaths
   1398 
   1399     def GetMapsToComposite(self, modifiers = None):
   1400         mapTypes = set()
   1401         if modifiers is None:
   1402             modifiers = self.GetSortedModifiers()
   1403         for modifier in modifiers:
   1404             mapTypes.update(modifier.ContributesToMapTypes())
   1405 
   1406         return mapTypes
   1407 
   1408     def GetPartsFromMaps(self, modifiers = None):
   1409         parts = set()
   1410         if modifiers is None:
   1411             modifiers = self.GetSortedModifiers()
   1412         for modifier in modifiers:
   1413             parts.update(modifier.GetAffectedTextureParts())
   1414 
   1415         return list(parts)
   1416 
   1417     def GetParts(self, modifiers = None):
   1418         parts = set()
   1419         if modifiers is None:
   1420             modifiers = self.GetSortedModifiers()
   1421         for modifier in iter(modifiers):
   1422             part = self.CategoryToPart(modifier.categorie)
   1423             parts.add(part)
   1424 
   1425         return list(parts)
   1426 
   1427     def HideModifiersByCategory(self, category):
   1428         for modifier in self.GetModifiersByCategory(category):
   1429             self.HideModifier(modifier)
   1430 
   1431     def HideModifiersByPart(self, part):
   1432         for modifier in self.GetModifiersByPart(part):
   1433             self.HideModifier(modifier)
   1434 
   1435     def HideModifier(self, modifier):
   1436         self.__dirty = True
   1437         modifier.IsHidden = True
   1438         self.__dirtyModifiersRemoved.append(modifier)
   1439         if modifier in self.__dirtyModifiersAdded:
   1440             self.__dirtyModifiersAdded.remove(modifier)
   1441 
   1442     def ShowModifiersByCategory(self, category):
   1443         self.__filterHidden = False
   1444         for modifier in self.GetModifiersByCategory(category):
   1445             self.ShowModifier(modifier)
   1446 
   1447         self.__filterHidden = True
   1448 
   1449     def ShowModifiersByPart(self, part):
   1450         self.__filterHidden = False
   1451         for modifier in self.GetModifiersByPart(part):
   1452             self.ShowModifier(modifier)
   1453 
   1454         self.__filterHidden = True
   1455 
   1456     def ShowModifier(self, modifier):
   1457         self.__dirty = True
   1458         modifier.IsHidden = False
   1459         self.__dirtyModifiersAdded.append(modifier)
   1460         if modifier in self.__dirtyModifiersRemoved:
   1461             self.__dirtyModifiersRemoved.remove(modifier)
   1462 
   1463     def GetHiddenModifiers(self):
   1464         self.__filterHidden = False
   1465         modifiers = []
   1466         for modifier in iter(self.GetModifiersAsList()):
   1467             if modifier.IsHidden:
   1468                 modifiers.append(modifier)
   1469 
   1470         self._filterHidden = True
   1471         return modifiers
   1472 
   1473     def GetBodyModifiers(self, remapToPart = False):
   1474         return self.GetModifiersByPart(pdDef.DOLL_PARTS.BODY)
   1475 
   1476     def GetHeadModifiers(self, remapToPart = False):
   1477         category = pdDef.DOLL_PARTS.HEAD
   1478         if remapToPart:
   1479             category = self.CategoryToPart(category, pdDef.DOLL_PARTS.HEAD)
   1480         return self.GetModifiersByCategory(category)
   1481 
   1482     def GetHairModifiers(self, remapToPart = False):
   1483         category = pdDef.DOLL_PARTS.HAIR
   1484         if remapToPart:
   1485             category = self.CategoryToPart(category, pdDef.DOLL_PARTS.HAIR)
   1486         return self.GetModifiersByCategory(category)
   1487 
   1488     def GetAccessoriesModifiers(self, remapToPart = False):
   1489         category = pdDef.DOLL_PARTS.ACCESSORIES
   1490         if remapToPart:
   1491             category = self.CategoryToPart(category, pdDef.DOLL_PARTS.ACCESSORIES)
   1492         return self.GetModifiersByCategory(category)
   1493 
   1494     def GetModifiersAsList(self, includeFuture = False, showHidden = False):
   1495         filterHiddenState = self.__filterHidden
   1496         if showHidden:
   1497             self.__filterHidden = False
   1498         ret = []
   1499         if includeFuture:
   1500             ret = list(self.__pendingModifiersToAdd)
   1501         for each in self.modifiersdata.itervalues():
   1502             ret.extend(each)
   1503 
   1504         self.__filterHidden = filterHiddenState
   1505         return ret
   1506 
   1507     @telemetry.ZONE_METHOD
   1508     def GetSortedModifiers(self, showHidden = False, includeFuture = False):
   1509         if showHidden:
   1510             modifiers = self.SortParts(self.GetModifiersAsList(includeFuture=includeFuture, showHidden=True))
   1511             return modifiers
   1512         if self.__dirty or not self.__sortedList:
   1513             self.__sortedList = self.SortParts(self.GetModifiersAsList())
   1514         if includeFuture:
   1515             ret = list(self.__pendingModifiersToAdd)
   1516             ret.extend(self.__sortedList)
   1517         return list(self.__sortedList)
   1518 
   1519     def _SortPartFunc(self, modifier, dso):
   1520         try:
   1521             dsoIdx = dso.index(modifier.categorie) * 1000
   1522             groups = pdDef.GROUPS.get(modifier.categorie, [])
   1523             try:
   1524                 subIdx = groups.index(modifier.group)
   1525             except ValueError:
   1526                 subIdx = 999
   1527 
   1528             dsoIdx += subIdx
   1529         except ValueError:
   1530             dsoIdx = -1
   1531 
   1532         return dsoIdx
   1533 
   1534     def SortParts(self, modifiersList):
   1535         dso = self.desiredOrder
   1536         retList = list(modifiersList)
   1537         retList.sort(key=lambda x: self._SortPartFunc(x, dso))
   1538         return retList
   1539 
   1540     def ChangeDesiredOrder(self, categoryA, categoryB):
   1541         aIdx = self.desiredOrder.index(categoryA)
   1542         bIdx = self.desiredOrder.index(categoryB)
   1543         if aIdx > bIdx:
   1544             self.desiredOrderChanged = True
   1545             self.__sortedList = None
   1546             self.desiredOrder[bIdx], self.desiredOrder[aIdx] = self.desiredOrder[aIdx], self.desiredOrder[bIdx]
   1547 
   1548     def GetMeshes(self, part = None, alternativeModifierList = None, includeClothMeshes = False):
   1549         meshes = []
   1550         parts = [part] if part else pdDef.DOLL_PARTS
   1551 
   1552         def CollectMeshsFrom(fromIter):
   1553             for each in iter(fromIter):
   1554                 if each.weight > 0:
   1555                     if part in (None, self.CategoryToPart(each.categorie)):
   1556                         for mesh in iter(each.meshes):
   1557                             meshes.insert(0, mesh)
   1558 
   1559                         if includeClothMeshes and each.clothData:
   1560                             meshes.insert(0, each.clothData)
   1561 
   1562         if alternativeModifierList is not None:
   1563             CollectMeshsFrom(alternativeModifierList)
   1564         elif part is not None:
   1565             for p in parts:
   1566                 CollectMeshsFrom(self.GetModifiersByPart(p))
   1567 
   1568         else:
   1569             CollectMeshsFrom(self.GetSortedModifiers())
   1570         return list(meshes)
   1571 
   1572     def RemapMeshes(self, destinationMeshes):
   1573         for modifier in iter(self.GetModifiersAsList()):
   1574             for mesh in iter(modifier.meshes):
   1575                 for destMesh in iter(destinationMeshes):
   1576                     if mesh.name == destMesh.name:
   1577                         mesh = destMesh
   1578                         break
   1579 
   1580     def CategoryToPart(self, category, partFilter = None):
   1581         if category in pdDef.DOLL_PARTS:
   1582             return category
   1583         if category in pdDef.BODY_CATEGORIES and partFilter in (None, pdDef.DOLL_PARTS.BODY):
   1584             return pdDef.DOLL_PARTS.BODY
   1585         if category in pdDef.HEAD_CATEGORIES and partFilter in (None, pdDef.DOLL_PARTS.HEAD):
   1586             return pdDef.DOLL_PARTS.HEAD
   1587         if category in pdDef.HAIR_CATEGORIES and partFilter in (None, pdDef.DOLL_PARTS.HAIR):
   1588             return pdDef.DOLL_PARTS.HAIR
   1589         if category in pdDef.ACCESSORIES_CATEGORIES and partFilter in (None, pdDef.DOLL_PARTS.ACCESSORIES):
   1590             return pdDef.DOLL_PARTS.ACCESSORIES
   1591         if category in pdDef.DOLL_EXTRA_PARTS.BODYSHAPES:
   1592             return pdDef.DOLL_EXTRA_PARTS.BODYSHAPES
   1593         if category in pdDef.DOLL_EXTRA_PARTS.UTILITYSHAPES:
   1594             return pdDef.DOLL_EXTRA_PARTS.UTILITYSHAPES
   1595         if category in pdDef.DOLL_EXTRA_PARTS.DEPENDANTS:
   1596             return pdDef.DOLL_PARTS.BODY
   1597         return pdDef.DOLL_EXTRA_PARTS.UNDEFINED