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