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

projectedDecals.py (22618B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\paperDoll\projectedDecals.py
      2 import trinity
      3 import uthread
      4 import blue
      5 import copy
      6 import yaml
      7 import eve.common.script.paperDoll.paperDollDefinitions as pdDef
      8 import eve.common.script.paperDoll.paperDollCommonFunctions as PD
      9 import eve.client.script.paperDoll.commonClientFunctions as pdCcF
     10 import eve.common.script.paperDoll.paperDollDataManagement as pdDm
     11 ST_SHADERRES = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDF.fx'
     12 RT_SHADERRES = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarTattooBRDF.fx'
     13 BK_SHADERRES = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarTattooBaking.fx'
     14 INV_SHADERRES = 'res:/Graphics/Effect/Managed/Interior/Avatar/InvisibleAvatar.fx'
     15 EO_SHADERRES = 'res:/Graphics/Effect/Utility/Compositing/ExpandOpaque.fx'
     16 MASK_SHADERRES = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarTattooMaskBaking.fx'
     17 DECAL_PROJECTION_DISABLED = -1.0
     18 DECAL_PROJECTION_PLANAR = 0.0
     19 DECAL_PROJECTION_CYLINDRICAL = 1.0
     20 DECAL_PROJECTION_CAMERA = 2.0
     21 DECAL_PROJECTION_EO_LOD_THRESHOLD = 1
     22 
     23 def ShowTexture(texture, showAlpha = False):
     24     from carbon.tools.jessica.app.preview import Preview
     25     Preview(texture)
     26 
     27 
     28 class DecalBaker(object):
     29 
     30     def __init__(self, factory):
     31         self.factory = factory
     32         self.__avatar = None
     33         self._gender = None
     34         self.bakeScene = None
     35         self.size = None
     36         self.isReady = False
     37         self.doingAvatar = False
     38         self.doingDecal = False
     39         self.bakingTasklet = None
     40         self.avatarShaderSettingTasklet = None
     41         self.decalSettingTasklet = None
     42         if pdDef.GENDER_ROOT:
     43             self.femalePath = 'res:\\Graphics\\Character\\Female\\Paperdoll'
     44             self.malePath = 'res:\\Graphics\\Character\\Male\\Paperdoll'
     45         else:
     46             self.femalePath = 'res:\\Graphics\\Character\\Modular\\Female'
     47             self.malePath = 'res:\\Graphics\\Character\\Modular\\Male'
     48         self.femaleBindPose = self.GetBindPose(pdDef.FEMALE_DECAL_BINDPOSE)
     49         self.maleBindPose = self.GetBindPose(pdDef.MALE_DECAL_BINDPOSE)
     50 
     51     def Initialize(self):
     52         self.doingAvatar = False
     53         self.doingDecal = False
     54 
     55     def HasAvatar(self):
     56         return self.__avatar is not None and type(self.__avatar) is trinity.Tr2IntSkinnedObject
     57 
     58     def GetBindPose(self, resPath):
     59         updater = trinity.Tr2GrannyAnimation()
     60         updater.resPath = resPath
     61         return updater
     62 
     63     def SetSize(self, size):
     64         if not (type(size) is tuple or type(size) is list) and len(size) == 2:
     65             raise TypeError('DecalBaker::SetSize - Size is not a tuple or list of length 2!')
     66         self.size = size
     67 
     68     def __DoBlendShapes(self, doll):
     69         self.factory.ApplyMorphTargetsToMeshes(self.__avatar.visualModel.meshes, doll.buildDataManager.GetMorphTargets())
     70 
     71     def __CreateNudeAvatar(self, doll):
     72         if doll.gender == pdDef.GENDER.FEMALE:
     73             genderPath = self.femalePath
     74         else:
     75             genderPath = self.malePath
     76         self._gender = doll.gender
     77         avatar = trinity.Tr2IntSkinnedObject()
     78         avatar.visualModel = self.factory.CreateVisualModel(doll.gender)
     79         avatar.name = 'TattooBakingAvatar'
     80         bodyParts = []
     81         for nudePartModularPath in pdDef.DEFAULT_NUDE_PARTS:
     82             bodyPartTuple = None
     83             if pdDef.DOLL_PARTS.HEAD in nudePartModularPath:
     84                 headMods = doll.buildDataManager.GetModifiersByCategory(pdDef.DOLL_PARTS.HEAD)
     85                 if headMods:
     86                     headMod = doll.buildDataManager.GetModifiersByCategory(pdDef.DOLL_PARTS.HEAD)[0]
     87                     if headMod.redfile:
     88                         projectionHead = blue.resMan.LoadObject(headMod.redfile)
     89                         for mesh in projectionHead.meshes:
     90                             for decalArea in mesh.decalAreas:
     91                                 f = decalArea.effect
     92                                 if f is None:
     93                                     continue
     94                                 for p in f.parameters:
     95                                     if p.name == 'CutMaskInfluence':
     96                                         p.value = 0.0
     97                                     elif p.name == 'DecalClip':
     98                                         p.value = 0.0
     99 
    100                                 mesh.opaqueAreas.append(decalArea)
    101                                 mesh.decalAreas.remove(decalArea)
    102 
    103                         bodyPartTuple = (headMod.name, projectionHead)
    104             if not bodyPartTuple:
    105                 mod = self.factory.CollectBuildData(self._gender, nudePartModularPath)
    106                 bodyPartTuple = (mod.name, blue.resMan.LoadObject(mod.redfile))
    107                 for mesh in bodyPartTuple[1].meshes:
    108                     for opaqueArea in mesh.opaqueAreas:
    109                         f = opaqueArea.effect
    110                         if f is None:
    111                             continue
    112                         for p in f.parameters:
    113                             if p.name == 'CutMaskInfluence':
    114                                 p.value = 0.0
    115                             elif p.name == 'DecalClip':
    116                                 p.value = 0.0
    117 
    118             bodyParts.append(bodyPartTuple)
    119 
    120         PD.BeFrameNice()
    121         for bodyPartTuple in bodyParts:
    122             modname, bodyPart = bodyPartTuple
    123             if bodyPart:
    124                 for mesh in bodyPart.meshes:
    125                     mesh.name = modname
    126                     if len(mesh.opaqueAreas) > 0:
    127                         areasCount = mesh.GetAreasCount()
    128                         for i in xrange(areasCount):
    129                             areas = mesh.GetAreas(i)
    130                             if areas and areas != mesh.opaqueAreas:
    131                                 del areas[:]
    132 
    133                         avatar.visualModel.meshes.append(mesh)
    134 
    135         self.__avatar = avatar
    136         self.__DoBlendShapes(doll)
    137         if doll.gender == pdDef.GENDER.FEMALE:
    138             bindPose = self.femaleBindPose
    139         else:
    140             bindPose = self.maleBindPose
    141         avatar.animationUpdater = bindPose
    142         clip = bindPose.grannyRes.GetAnimationName(0)
    143         bindPose.PlayAnimationEx(clip, 0, 0, 0)
    144         avatar.ResetAnimationBindings()
    145         bindPose.EndAnimation()
    146 
    147     def CreateTargetAvatarFromDoll(self, doll):
    148         self.doingAvatar = True
    149         self.__CreateNudeAvatar(doll)
    150 
    151         def PrepareAvatar_t():
    152             try:
    153                 self.SetShader_t(self.__avatar, [], BK_SHADERRES, False)
    154             finally:
    155                 self.doingAvatar = False
    156 
    157         self.avatarShaderSettingTasklet = uthread.new(PrepareAvatar_t)
    158         uthread.schedule(self.avatarShaderSettingTasklet)
    159 
    160     def CreateTargetsOnModifier(self, modifier, asRenderTargets = False):
    161         textureFormat = trinity.PIXEL_FORMAT.B8G8R8A8_UNORM
    162 
    163         def validateTarget(target, width, height):
    164             if not (target and target.isGood and target.depth > 0):
    165                 return False
    166             if target.width != width or target.height != height:
    167                 return False
    168             return True
    169 
    170         if modifier.decalData.bodyEnabled:
    171             modifier.mapD[pdDef.DOLL_PARTS.BODY] = trinity.TriTextureRes(trinity.Tr2RenderTarget(self.size[0] / 2, self.size[1], 1, textureFormat))
    172         if modifier.decalData.headEnabled:
    173             modifier.mapD[pdDef.DOLL_PARTS.HEAD] = trinity.TriTextureRes(trinity.Tr2RenderTarget(self.size[0] / 2, self.size[1] / 2, 1, textureFormat))
    174 
    175     def BakeDecalToModifier(self, decal, projectedDecalModifier):
    176         while self.doingAvatar:
    177             PD.Yield(frameNice=False)
    178 
    179         if type(self.__avatar) is not trinity.Tr2IntSkinnedObject:
    180             raise TypeError('DecalBaker::DoPrepare - Avatar is not set!')
    181         if type(decal) is not ProjectedDecal:
    182             raise TypeError('DecalBaker::BakeDecalToModifier - decal is not an instance of PaperDoll::ProjectedDecal!')
    183         self.isReady = False
    184         self.doingDecal = False
    185         if projectedDecalModifier.decalData != decal:
    186             projectedDecalModifier.decalData = decal
    187 
    188         def PrepareDecal_t():
    189             self.doingDecal = True
    190             self.SetDecal_t(self.__avatar, decal, True, True)
    191             self.doingDecal = False
    192 
    193         self.decalSettingTasklet = uthread.new(PrepareDecal_t)
    194         self.CreateTargetsOnModifier(projectedDecalModifier)
    195         self.bakingTasklet = uthread.new(self.DoBake_t, projectedDecalModifier)
    196 
    197     def _ExpandOpaque(self, bodyPart, targetTex):
    198         eoMaskPath = 'res:/graphics/character/global/tattoomask/{0}_opaque_mask_{1}.dds'.format(self._gender, bodyPart)
    199         eoMaskRes = blue.resMan.GetResource(eoMaskPath)
    200         fx = trinity.Tr2Effect()
    201         fx.effectFilePath = EO_SHADERRES
    202         while not eoMaskRes.isPrepared or not fx.effectResource.isPrepared:
    203             PD.Yield()
    204 
    205         if not eoMaskRes.isGood:
    206             raise ValueError('Paperdoll::Opaque mask resource failed to load.')
    207         if not fx.effectResource.isGood:
    208             raise ValueError('Paperdoll::Effect resource failed to load.')
    209         tex = trinity.TriTextureParameter()
    210         tex.name = 'Mask'
    211         tex.SetResource(eoMaskRes)
    212         fx.resources.append(tex)
    213         v = trinity.Tr2Vector2Parameter()
    214         v.name = 'gMaskSize'
    215         v.value = (eoMaskRes.width, eoMaskRes.height)
    216         fx.parameters.append(v)
    217         tex = trinity.TriTextureParameter()
    218         tex.name = 'Texture'
    219         tex.SetResource(targetTex)
    220         fx.resources.append(tex)
    221         v = trinity.Tr2Vector2Parameter()
    222         v.name = 'gTextureSize'
    223         v.value = (targetTex.width, targetTex.height)
    224         fx.parameters.append(v)
    225         fx.RebuildCachedData()
    226         vp = trinity.TriViewport()
    227         vp.width = targetTex.width
    228         vp.height = targetTex.height
    229         expandedRT = trinity.Tr2RenderTarget(vp.width, vp.height, 1, trinity.PIXEL_FORMAT.B8G8R8A8_UNORM)
    230         rj = trinity.CreateRenderJob('Expanding Opaque')
    231         rj.PushRenderTarget(expandedRT)
    232         rj.SetProjection(trinity.TriProjection())
    233         rj.SetView(trinity.TriView())
    234         rj.SetViewport(vp)
    235         rj.PushDepthStencil(None)
    236         rj.Clear((0.0, 0.0, 0.0, 0.0))
    237         rj.SetStdRndStates(trinity.RM_FULLSCREEN)
    238         rj.SetRenderState(trinity.D3DRS_SEPARATEALPHABLENDENABLE, 1)
    239         rj.SetRenderState(trinity.D3DRS_SRCBLENDALPHA, trinity.TRIBLEND_ONE)
    240         rj.SetRenderState(trinity.D3DRS_DESTBLENDALPHA, trinity.TRIBLEND_ZERO)
    241         rj.RenderEffect(fx)
    242         rj.SetRenderTarget(targetTex.wrappedRenderTarget)
    243         rj.RenderTexture(expandedRT)
    244         rj.PopRenderTarget()
    245         rj.PopDepthStencil()
    246         rj.ScheduleChained()
    247         rj.WaitForFinish()
    248 
    249     def _Bake(self, targetTex, transform):
    250         self.SetShaderValue(self.__avatar, 'TattooVSUVTransform', transform)
    251         size = (targetTex.width, targetTex.height)
    252         renderTarget = targetTex.wrappedRenderTarget
    253         sourceVP = trinity.TriViewport()
    254         sourceVP.width = size[0]
    255         sourceVP.height = size[1]
    256         rj = trinity.CreateRenderJob('Baking out source decal texture')
    257         rj.PushRenderTarget(renderTarget)
    258         rj.SetProjection(trinity.TriProjection())
    259         rj.SetView(trinity.TriView())
    260         rj.SetViewport(sourceVP)
    261         rj.PushDepthStencil(None)
    262         rj.Clear((0.0, 0.0, 0.0, 0.0))
    263         rj.SetStdRndStates(trinity.RM_FULLSCREEN)
    264         rj.Update(self.bakeScene)
    265         rj.RenderScene(self.bakeScene)
    266         rj.PopRenderTarget()
    267         rj.PopDepthStencil()
    268         rj.ScheduleChained()
    269         rj.WaitForFinish()
    270 
    271     def DoBake_t(self, projectedDecalModifier):
    272         while self.doingDecal or self.doingAvatar:
    273             PD.Yield()
    274 
    275         self.bakeScene = trinity.WodBakingScene()
    276         self.bakeScene.Avatar = self.__avatar
    277         if projectedDecalModifier.decalData.bodyEnabled:
    278             while not projectedDecalModifier.mapD[pdDef.DOLL_PARTS.BODY].isPrepared:
    279                 PD.Yield(frameNice=False)
    280 
    281             if not projectedDecalModifier.mapD[pdDef.DOLL_PARTS.BODY].isGood:
    282                 raise ValueError('Paperdoll::Decal modifier body diffuse map failed to load.')
    283             bodyTex = projectedDecalModifier.mapD[pdDef.DOLL_PARTS.BODY]
    284             self._Bake(bodyTex, (0.0, 0.0, 2.0, 1.0))
    285             self._ExpandOpaque(pdDef.DOLL_PARTS.BODY, bodyTex)
    286         elif projectedDecalModifier.mapD.get(pdDef.DOLL_PARTS.BODY):
    287             del projectedDecalModifier.mapD[pdDef.DOLL_PARTS.BODY]
    288         if projectedDecalModifier.decalData.headEnabled:
    289             while not projectedDecalModifier.mapD[pdDef.DOLL_PARTS.HEAD].isPrepared:
    290                 PD.Yield(frameNice=False)
    291 
    292             if not projectedDecalModifier.mapD[pdDef.DOLL_PARTS.HEAD].isGood:
    293                 raise ValueError('Paperdoll::Decal modifier head diffuse map failed to load.')
    294             headTex = projectedDecalModifier.mapD[pdDef.DOLL_PARTS.HEAD]
    295             self._Bake(headTex, (-0.5, 0.0, 2.0, 2.0))
    296             self._ExpandOpaque(pdDef.DOLL_PARTS.HEAD, headTex)
    297         elif projectedDecalModifier.mapD.get(pdDef.DOLL_PARTS.HEAD):
    298             del projectedDecalModifier.mapD[pdDef.DOLL_PARTS.HEAD]
    299         self.bakeScene = None
    300         self.isReady = True
    301 
    302     def SetShader_t(self, avatar, targetsToIgnore, shaderres, allTargets = False):
    303         try:
    304             effect = trinity.Tr2Effect()
    305             effect.effectFilePath = shaderres
    306             while not effect.effectResource.isPrepared:
    307                 PD.Yield()
    308 
    309             if not effect.effectResource.isGood:
    310                 raise TaskletExit('Paperdoll::Effect resource failed to load for shader %s' % shaderres)
    311             for mesh in avatar.visualModel.meshes:
    312                 areasList = [mesh.opaqueAreas, mesh.decalAreas, mesh.transparentAreas]
    313                 if allTargets or mesh.name not in targetsToIgnore:
    314                     for areas in areasList:
    315                         for area in areas:
    316                             transformUV = None
    317                             for p in area.effect.parameters:
    318                                 if p.name == 'TransformUV0':
    319                                     transformUV = p.value
    320                                     break
    321 
    322                             area.effect.effectFilePath = shaderres
    323                             area.effect.PopulateParameters()
    324                             area.effect.RebuildCachedData()
    325                             for p in area.effect.parameters:
    326                                 if p.name == 'TransformUV0':
    327                                     p.value = transformUV
    328                                     break
    329 
    330         except TaskletExit:
    331             raise
    332 
    333     def SetShaderValue(self, avatar, name, value):
    334         for mesh in avatar.visualModel.meshes:
    335             for effect in pdCcF.GetEffectsFromMesh(mesh):
    336                 effect.StartUpdate()
    337                 for p in effect.parameters:
    338                     if p.name == name:
    339                         p.value = value
    340                         break
    341 
    342                 effect.RebuildCachedData()
    343                 effect.EndUpdate()
    344 
    345     def SetDecal_t(self, avatar, decal, setTexture, setMask):
    346         if decal is None:
    347             return
    348         while self.doingAvatar or decal.BusyLoading():
    349             PD.Yield(frameNice=False)
    350 
    351         if not decal.maskResource.isGood:
    352             raise ValueError('Paperdoll::Decal mask resource failed to load.')
    353         if not decal.textureResource.isGood:
    354             raise ValueError('Paperdoll::Decal texture resource failed to load.')
    355         for mesh in iter(avatar.visualModel.meshes):
    356             for effect in pdCcF.GetEffectsFromMesh(mesh):
    357                 effect.StartUpdate()
    358                 for p in effect.parameters:
    359                     valuesToSet = None
    360                     if p.name == 'TattooYawPitchRoll':
    361                         valuesToSet = decal.GetYPR()
    362                     elif p.name == 'TattooPosition':
    363                         valuesToSet = decal.GetPositionAndScale()
    364                     elif p.name == 'TattooOptions':
    365                         valuesToSet = decal.GetOptions()
    366                     elif p.name == 'TattooDimensions':
    367                         valuesToSet = decal.GetDimensions()
    368                     elif p.name == 'TattooAspectRatio' and hasattr(decal, 'aspectRatio'):
    369                         valuesToSet = decal.aspectRatio
    370                     elif p.name == 'TattooPickingLayer':
    371                         valuesToSet = decal.layer
    372                     if type(valuesToSet) == tuple:
    373                         valLen = len(valuesToSet)
    374                         if valLen < len(p.value):
    375                             valuesToSet = valuesToSet + p.value[valLen:]
    376                     if valuesToSet:
    377                         p.value = valuesToSet
    378 
    379                 resourceNameFound = False
    380                 if setTexture:
    381                     resourceNameFound = self.SetTexture_t('TattooTextureMap', decal.textureResource, effect)
    382                 if setMask and resourceNameFound:
    383                     resourceNameFound = self.SetTexture_t('TattooTextureMask', decal.maskResource, effect)
    384                 effect.RebuildCachedData()
    385                 effect.EndUpdate()
    386 
    387     def SetTexture_t(self, name, texture, effect):
    388         for i, r in enumerate(effect.resources):
    389             if r.name == name:
    390                 r.SetResource(texture)
    391                 return True
    392 
    393         return False
    394 
    395 
    396 class ProjectedDecal(object):
    397 
    398     def __init__(self):
    399         self.layer = 0
    400         self.azimuth = 0.0
    401         self.incline = 1.5707963267948966
    402         self.bodyEnabled = True
    403         self.headEnabled = True
    404         self.posx = 0.0
    405         self.posy = 0.0
    406         self.posz = 0.0
    407         self.scale = 0.0
    408         self.aspectRatio = 1.0
    409         self.angleRotation = 180.0
    410         self.mode = DECAL_PROJECTION_DISABLED
    411         self.flipx = False
    412         self.flipy = False
    413         self.offsety = 0.0
    414         self.offsetx = 0.0
    415         self.radius = 2.0
    416         self.height = 4.0
    417         self.yaw = 0.0
    418         self.pitch = 0.0
    419         self.roll = 0.0
    420         self.planarBeta = 0.0
    421         self.planarScale = 0.0
    422         self.maskPathEnabled = False
    423         self.texturePath = ''
    424         self.maskPath = ''
    425         self.label = ''
    426         self.textureResource = None
    427         self.maskResource = None
    428 
    429     def __str__(self):
    430         return yaml.dump(self.__getstate__(), default_flow_style=False, Dumper=yaml.CDumper)
    431 
    432     def __getstate__(self):
    433         state = dict()
    434         for key, value in self.__dict__.iteritems():
    435             if type(value) is not trinity.TriTextureRes:
    436                 state[key] = copy.copy(value)
    437             else:
    438                 state[key] = None
    439 
    440         return state
    441 
    442     def __eq__(self, other):
    443         if not other:
    444             return False
    445         typesThatMatter = (float,
    446          bool,
    447          str,
    448          int)
    449         for key, val in other.__dict__.iteritems():
    450             if key not in self.__dict__:
    451                 return False
    452             if type(self.__dict__[key]) in typesThatMatter:
    453                 if self.__dict__[key] != val:
    454                     return False
    455 
    456         return True
    457 
    458     def __ne__(self, other):
    459         return not self.__eq__(other)
    460 
    461     def BusyLoading(self):
    462         if self.texturePath and not self.textureResource:
    463             raise AttributeError('PaperDoll::ProjectedDecal - Decal has texturepath defined but no texture resource and is not loading it!')
    464         if self.maskPath and not self.maskResource:
    465             raise AttributeError('PaperDoll::ProjectedDecal - Decal has maskpath defined but no texture resource and is not loading it!')
    466         loading = self.textureResource and not self.textureResource.isPrepared or self.maskResource and not self.maskResource.isPrepared
    467         return loading
    468 
    469     def __GetResourceSetPath(self, path, pathMember):
    470         if path:
    471             setattr(self, pathMember, path)
    472             return blue.resMan.GetResource(str(path))
    473         else:
    474             return None
    475 
    476     def SetTexturePath(self, path):
    477         self.textureResource = self.__GetResourceSetPath(path, 'texturePath')
    478 
    479     def SetMaskPath(self, path):
    480         self.maskResource = self.__GetResourceSetPath(path, 'maskPath')
    481 
    482     def SetCylindricalProjection(self):
    483         self.mode = DECAL_PROJECTION_CYLINDRICAL
    484 
    485     def SetPlanarProjection(self):
    486         self.mode = DECAL_PROJECTION_PLANAR
    487 
    488     def SetDisabledProjection(self):
    489         self.mode = DECAL_PROJECTION_DISABLED
    490 
    491     def SetPosition(self, posx, posy, posz):
    492         self.posx = posx
    493         self.posy = posy
    494         self.posz = posz
    495 
    496     def GetPositionAndScale(self):
    497         return (self.posx,
    498          self.posy,
    499          self.posz,
    500          self.scale)
    501 
    502     def SetOptions(self, angleRotation, flipX, flipY, offsetY = 0.0, offsetX = 0.0):
    503         self.angleRotation = angleRotation
    504         self.flipx = flipX
    505         self.flipy = flipY
    506         self.offsety = offsetY
    507         self.offsetx = offsetX
    508 
    509     def GetOptions(self):
    510         flipPack = 10.0 * float(self.flipy) + float(self.flipx)
    511         return (self.angleRotation,
    512          flipPack,
    513          self.offsety,
    514          self.offsetx)
    515 
    516     def SetDimensions(self, radius, height):
    517         self.radius = radius
    518         self.height = height
    519 
    520     def GetDimensions(self):
    521         if self.mode == DECAL_PROJECTION_PLANAR:
    522             return (self.planarBeta, self.planarScale, float(self.maskPathEnabled))
    523         else:
    524             return (self.radius, self.height, float(self.maskPathEnabled))
    525 
    526     def SetYPR(self, yaw, pitch, roll):
    527         self.yaw = yaw
    528         self.pitch = pitch
    529         self.roll = roll
    530 
    531     def GetYPR(self):
    532         return (self.yaw,
    533          self.pitch,
    534          self.roll,
    535          self.mode)
    536 
    537     @staticmethod
    538     def Load(source):
    539         inst = source
    540         if isinstance(source, basestring):
    541             inst = pdDm.LoadYamlFileNicely(source)
    542         projectedDecal = ProjectedDecal()
    543         for key, val in inst.__dict__.iteritems():
    544             if key in projectedDecal.__dict__:
    545                 projectedDecal.__dict__[key] = val
    546 
    547         if inst.texturePath:
    548             projectedDecal.SetTexturePath(inst.texturePath)
    549         if inst.maskPath:
    550             projectedDecal.SetMaskPath(inst.maskPath)
    551         return projectedDecal
    552 
    553     @staticmethod
    554     def Save(source, resPath):
    555         f = file(resPath, 'w')
    556         yaml.dump(source, f, default_flow_style=False, Dumper=yaml.CDumper)
    557         f.flush()
    558         f.close()