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

decalValidation.py (15373B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\evegraphics\validation\validators\decalValidation.py
      2 import os
      3 import blue
      4 import trinity
      5 from platformtools.compatibility.exposure.artpipeline import decalutils
      6 from evegraphics.validation.commonUtilities import Validate, IsContent
      7 from evegraphics.validation.validationFunctions import ListAttributesAreDistinct
      8 from evegraphics.validation import resources
      9 from evegraphics.validation.validators import sofHullValidation
     10 from evegraphics.validation.validators import limits
     11 from trinity.effects import GetMergedParameters
     12 MAX_DECAL_PRIMITIVE_COUNT = 1000
     13 _VALID_FXPATH_LOC = 'res:/graphics/effect/managed/space/decals'
     14 FACTION_PATH = 'res:/dx9/model/spaceobjectfactory/factions'
     15 
     16 def GetFactionVisibilityGroups(context):
     17     factionVisibilityGroups = context.cache.get('factionVisibilityGroups', None)
     18     if factionVisibilityGroups is None:
     19         factions = []
     20         factionVisibilityGroups = {}
     21         for f in blue.paths.listdir(FACTION_PATH):
     22             if os.path.splitext(f)[1].lower() == '.red':
     23                 factions.append(resources.GetResource(context, FACTION_PATH + '/' + f, resources.ResourceType.OBJECT))
     24 
     25         for faction in factions:
     26             if hasattr(faction, 'visibilityGroupSet'):
     27                 factionVisibilityGroups[faction] = [ g.str for g in getattr(faction.visibilityGroupSet, 'visibilityGroups', []) ]
     28             else:
     29                 factionVisibilityGroups[faction] = []
     30 
     31         context.cache['factionVisibilityGroups'] = factionVisibilityGroups
     32     return factionVisibilityGroups
     33 
     34 
     35 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSet(decalSet)')
     36 def ValidateDecalSet(context, owner, decalSet):
     37     if decalSet.name == '':
     38         context.Error(decalSet, 'DecalSet has no name')
     39     if decalSet.visibilityGroup is None or decalSet.visibilityGroup == '':
     40         context.Error(decalSet, 'DecalSet %s has no visibilityGroup set' % str(decalSet.name))
     41     elif decalSet.visibilityGroup != decalSet.visibilityGroup.lower():
     42         context.Error(decalSet, 'DecalSet %s has a visibilityGroup with uppercase letters %s ' % (str(decalSet.name), str(decalSet.visibilityGroup)))
     43     killmarkCount = limits.GetValue(owner.category, limits.KILLMARK_DECAL)
     44     shouldHaveKillmarks = killmarkCount is not None and killmarkCount > 0
     45     if decalSet.visibilityGroup == 'primary':
     46         killcounters = [ item.name for item in decalSet.items if item.usage == trinity.DecalUsage.KillCounter ]
     47         if shouldHaveKillmarks and len(killcounters) == 0:
     48             context.Error(decalSet, 'DecalSet %s has no decals with usage KillCounter' % str(decalSet.name))
     49         elif not shouldHaveKillmarks and len(killcounters) > 0:
     50             context.Error(decalSet, "DecalSet %s has decals with usage KillCounter, but the owner category '%s' shouldn't have any" % (str(decalSet.name), str(owner.category)))
     51 
     52 
     53 @Validate('EveSOFDataHull(owner)/.../List[EveSOFDataHullDecalSet](decalSets)')
     54 def ValidateDecalLimit(context, owner, decalSets):
     55     factionVisibilityGroups = GetFactionVisibilityGroups(context)
     56     nonLogoDecalCountByVisibilityGroup = {decalSet.visibilityGroup:[ item for item in decalSet.items if item.usage != trinity.DecalUsage.Logo ] for decalSet in decalSets}
     57     logoDecalCountByVisibilityGroup = {decalSet.visibilityGroup:[ item for item in decalSet.items if item.usage == trinity.DecalUsage.Logo ] for decalSet in decalSets}
     58     decalLimit = limits.GetValue(owner.category, limits.MAX_DECAL_COUNT)
     59     decalLimit = 0 if decalLimit is None else decalLimit
     60     factionDecalCount = {}
     61     for faction, visibilityGroups in factionVisibilityGroups.iteritems():
     62         decalCount = 0
     63         for factionVisibilityGroup in visibilityGroups:
     64             decalCount += len(nonLogoDecalCountByVisibilityGroup.get(factionVisibilityGroup, []))
     65             if faction.logoSet is not None:
     66                 logoDecals = logoDecalCountByVisibilityGroup.get(factionVisibilityGroup, [])
     67                 for logoDecal in logoDecals:
     68                     factionLogos = [faction.logoSet.Primary,
     69                      faction.logoSet.Secondary,
     70                      faction.logoSet.Tertiary,
     71                      faction.logoSet.Marking_01,
     72                      faction.logoSet.Marking_02]
     73                     if factionLogos[logoDecal.logoType] is not None:
     74                         decalCount += 1
     75 
     76         factionDecalCount[faction.name] = decalCount
     77 
     78     offendingFactions = {factionName:decalCount for factionName, decalCount in factionDecalCount.iteritems() if decalCount > decalLimit}
     79     if len(offendingFactions):
     80         message = 'Hull exceeds decal limit of %s with a highest count of %s.\nThis limit is exceeded when hull is combined with the following factions;\n\n\t- %s' % (decalLimit, max(offendingFactions.values()), ',\n\t- '.join(offendingFactions.keys()))
     81         context.Error(owner, message)
     82 
     83 
     84 @Validate('EveSOFDataHullDecalSet')
     85 def ValidateNoRepetitionIsWithinDecalSet(context, decalSet):
     86     context.Expect(decalSet.items, None, ListAttributesAreDistinct('name'))
     87     context.Expect(decalSet.items, None, ListAttributesAreDistinct(None, listAttributesToIgnore=['name']))
     88 
     89 
     90 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSet(decalSet)')
     91 def ValidateAllDecalsUseTheSameParentTextures(context, owner, decalSet):
     92     allAreas = sofHullValidation._GetAllHullAreas(owner)
     93     usedMaps = {'NormalMap': [],
     94      'DirtMap': []}
     95     for area in allAreas:
     96         for tex in area.textures:
     97             if tex.name in usedMaps.iterkeys() and tex.resFilePath.lower() not in usedMaps[tex.name]:
     98                 usedMaps[tex.name].append(tex.resFilePath.lower())
     99 
    100     for decal in decalSet.items:
    101         for tex in decal.textures:
    102             if tex.name in usedMaps.iterkeys():
    103                 if tex.resFilePath.lower() not in usedMaps[tex.name]:
    104                     context.Error(decal, 'Decal ' + str(decal.name) + ' uses wrong ' + str(tex.name) + ': ' + str(tex.resFilePath.lower()))
    105 
    106 
    107 @Validate('EveSOFDataHullDecalSetItem')
    108 def ValidateDecalSetItemTextures(context, decal):
    109     textures = []
    110     if decal.usage == trinity.DecalUsage.KillCounter:
    111         textures = ['DecalTransparencyMap']
    112     if decal.usage in (trinity.DecalUsage.Glow, trinity.DecalUsage.GlowCylindrical):
    113         textures = ['DecalTransparencyMap', 'DecalGlowMap']
    114     elif decal.usage == trinity.DecalUsage.Hole:
    115         textures = ['DecalInsideCubeMap', 'DecalHoleMap', 'DecalTransparencyMap']
    116     elif decal.usage in (trinity.DecalUsage.Standard, trinity.DecalUsage.Cylindrical):
    117         textures = ['DecalTransparencyMap',
    118          'DecalNormalMap',
    119          'DecalFresnelMap',
    120          'DecalAlbedoMap',
    121          'DecalRoughnessMap']
    122     _ValidateDecalSetItem(context, decal, textures)
    123 
    124 
    125 def _ValidateDecalSetItem(context, decal, textureNames):
    126     texturesByName = {tex.name:tex.resFilePath for tex in decal.textures}
    127     missingTextures = set(textureNames).difference(set(texturesByName.keys()))
    128     if len(missingTextures) > 0:
    129         context.Error(decal, 'DecalSetItem %s has a missing textures %s' % (str(decal.name), missingTextures))
    130     unsetTextures = [ textureName for textureName, texturePath in texturesByName.iteritems() if texturePath is None or texturePath == '' ]
    131     if len(unsetTextures) > 0:
    132         context.Error(decal, 'DecalSetItem %s has unset textures %s' % (str(decal.name), unsetTextures))
    133     if decal.usage == trinity.DecalUsage.Logo and len(decal.textures) > 0:
    134         context.Error(decal, 'DecalSetItem %s is used as a logo but has textures %s. The textures should come from the factional logoset' % (str(decal.name), texturesByName.keys()))
    135     else:
    136         unusedTextures = set(texturesByName.keys()).difference(set(textureNames))
    137         if len(unusedTextures) > 0:
    138             context.Error(decal, 'DecalSetItem %s has a unused textures %s' % (str(decal.name), unusedTextures))
    139 
    140 
    141 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSetItem(decal)', IsContent(IsContent.CONTENT))
    142 def DecalHasGeometry(context, owner, decal):
    143     grannyPath = owner.geometryResFilePath
    144     if not grannyPath:
    145         return
    146     ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath))
    147     if not ibs or not ibs[0]:
    148         context.Error(decal, 'decal is not touching any geometry')
    149     for ib in ibs:
    150         if len(ibs) / 3 > MAX_DECAL_PRIMITIVE_COUNT:
    151             context.Error(decal, 'too many triangles for the decal (maximum is %s)' % MAX_DECAL_PRIMITIVE_COUNT)
    152 
    153 
    154 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSetItem(decal)', IsContent(IsContent.BRANCH))
    155 def SofDecalHasGeometry(context, owner, decal):
    156     grannyPath = owner.geometryResFilePath
    157     if not grannyPath:
    158         return
    159     existing = [ x.GetIndices() for x in decal.indexBuffers ]
    160     ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath))
    161     if existing != ibs:
    162         context.Error(decal, 'decal persisted index buffer is out of date')
    163 
    164 
    165 @Validate('EveSpaceObjectDecal')
    166 def ValidateEveSpaceObjectDecal(context, decal):
    167     if [ each for each in decal.GetDecalPrimitiveCounts() if each >= MAX_DECAL_PRIMITIVE_COUNT ]:
    168         context.Error(decal, 'decal index buffer is too large')
    169 
    170 
    171 @Validate('List[EveSpaceObjectDecal]')
    172 def ValidateListOfEveSpaceObjectDecal(context, decals):
    173     context.Expect(decals, None, ListAttributesAreDistinct(None))
    174 
    175 
    176 @Validate('EveSpaceObjectDecal', IsContent(IsContent.BRANCH))
    177 def PublishedDecalHasIB(context, decal):
    178     if not decal.hasStaticIndexBuffers:
    179         context.Error(decal, 'decal needs to have index buffers')
    180 
    181 
    182 @Validate('EveSpaceObjectDecal', IsContent(IsContent.CONTENT))
    183 def ContentDecalHasNoIB(context, decal):
    184     if decal.hasStaticIndexBuffers:
    185         context.Error(decal, 'decal should not have index buffers')
    186 
    187 
    188 @Validate('EveSpaceObject2(owner)/.../EveSpaceObjectDecal(decal)', IsContent(IsContent.CONTENT))
    189 def DecalHasGeometry(context, owner, decal):
    190     mesh = owner.mesh
    191     if isinstance(mesh, trinity.Tr2Mesh):
    192         grannyPath = mesh.geometryResPath
    193     else:
    194         return
    195     if not grannyPath:
    196         return
    197     ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath))
    198     if not ibs or not ibs[0]:
    199         context.Error(decal, 'decal is not touching any geometry')
    200     if len(ibs[0]) / 3 > MAX_DECAL_PRIMITIVE_COUNT:
    201         context.Error(decal, 'too many triangles for the decal (maximum is %s)' % MAX_DECAL_PRIMITIVE_COUNT)
    202 
    203 
    204 @Validate('EveSpaceObject2(owner)/.../EveSpaceObjectDecal(decal)', IsContent(IsContent.BRANCH))
    205 def DecalHasGeometry(context, owner, decal):
    206     mesh = owner.mesh
    207     if isinstance(mesh, trinity.Tr2Mesh):
    208         grannyPath = mesh.geometryResPath
    209     else:
    210         return
    211     if not grannyPath:
    212         return
    213     existing = decal.GetStaticIndexBuffers()
    214     ibs = decalutils.ComputeDecalIndexBuffers(decal.position, decal.rotation, decal.scaling, blue.paths.ResolvePath(grannyPath))
    215     if existing != ibs:
    216         context.Error(decal, 'decal persisted index buffer is out of date')
    217 
    218 
    219 @Validate('EveSpaceObjectDecal')
    220 def DecalNeedsAnEffect(context, decal):
    221     if not decal.decalEffect:
    222         context.Error(decal, 'decal is missing an effect')
    223 
    224 
    225 @Validate('EveSpaceObjectDecal')
    226 def ValidDecalEffectPath(context, decal):
    227     if decal.decalEffect and decal.decalEffect.effectFilePath:
    228         if not decal.decalEffect.effectFilePath.lower().startswith(_VALID_FXPATH_LOC):
    229             context.Error(decal, 'decal effect must be in %s' % _VALID_FXPATH_LOC)
    230 
    231 
    232 @Validate('EveSOFDataHull(owner)/.../EveSOFDataHullDecalSetItem(decal)')
    233 def SofDecalMeshIndexIsValid(context, owner, decal):
    234     areas = set([ x.index for x in owner.opaqueAreas ])
    235     if decal.meshIndex >= 0 and decal.meshIndex not in areas:
    236         context.Error(decal, 'Mesh index does not reference any opaque area')
    237 
    238 
    239 @Validate('EveSOFDataHullDecalSetItem')
    240 def SofDecalWithParentTexturesShouldHaveMeshIndex(context, decalSetItem):
    241     if decalSetItem.meshIndex >= 0:
    242         return
    243     generic = resources.GetResource(context, 'res:/dx9/model/spaceobjectfactory/generic.red', resources.ResourceType.OBJECT)
    244     if not generic:
    245         return
    246     decalShader = _GetDecalSetItemShader(decalSetItem)
    247     for each in generic.decalShaders:
    248         if each.shader == decalShader:
    249             if each.parentTextures:
    250                 context.Error(decalSetItem, 'Decal with parent textures must have meshIndex specified')
    251 
    252 
    253 def _GetDecalSetItemShader(decalSetItem):
    254     shaderToPick = {trinity.DecalUsage.KillCounter: 'decalcounterv5.fx',
    255      trinity.DecalUsage.Standard: 'decalv5.fx',
    256      trinity.DecalUsage.Cylindrical: 'decalcylindricv5.fx',
    257      trinity.DecalUsage.Glow: 'decalglowv5.fx',
    258      trinity.DecalUsage.GlowCylindrical: 'decalglowcylindricv5.fx',
    259      trinity.DecalUsage.Logo: 'decalv5.fx',
    260      trinity.DecalUsage.Hole: 'decalholev5.fx'}
    261     shader = shaderToPick[decalSetItem.usage]
    262     return shader
    263 
    264 
    265 def _GetParentTextures(context, decalSetItem):
    266     generic = resources.GetResource(context, 'res:/dx9/model/spaceobjectfactory/generic.red', resources.ResourceType.OBJECT)
    267     if not generic:
    268         return []
    269     decalShader = _GetDecalSetItemShader(decalSetItem)
    270     for each in generic.decalShaders:
    271         if each.shader == decalShader:
    272             return [ x.str for x in each.parentTextures ]
    273 
    274     return []
    275 
    276 
    277 @Validate('EveSOFDataHullDecalSetItem(decal)/.../EveSOFDataTexture(texture)')
    278 def SofDecalDoesNotContainParentTextures(context, decal, texture):
    279     if decal.meshIndex < 0:
    280         return
    281     textures = _GetParentTextures(context, decal)
    282     if texture.name in textures:
    283         context.Error(texture, 'Decal texture is inherited from the hull and needs to be deleted')
    284 
    285 
    286 def _GetSofDecalSetItemShaderPath(context, decaSetItem):
    287     generic = resources.GetResource(context, 'res:/dx9/model/spaceobjectfactory/generic.red', resources.ResourceType.OBJECT)
    288     if not generic:
    289         return ''
    290     decalShader = _GetDecalSetItemShader(decaSetItem)
    291     head, tail = os.path.split(decalShader)
    292     return '%s/%s/%s%s' % (generic.decalShaderLocation,
    293      head,
    294      generic.shaderPrefix,
    295      tail)
    296 
    297 
    298 @Validate('EveSOFDataHullDecalSetItem')
    299 def ValidateSofDecalTextures(context, decalSetItem):
    300     shaderPath = _GetSofDecalSetItemShaderPath(context, decalSetItem)
    301     if not shaderPath or not blue.paths.exists(shaderPath):
    302         return
    303     params, res, _ = GetMergedParameters(shaderPath)
    304     decalShader = _GetDecalSetItemShader(decalSetItem)
    305     for each in decalSetItem.textures:
    306         if each.name not in res:
    307             context.Error(each, 'Texture %s is not used by shader %s' % (each.name, decalShader))
    308 
    309 
    310 @Validate('EveSOFDataHullDecalSetItem')
    311 def ValidateSofDecalParameters(context, decalSetItem):
    312     shaderPath = _GetSofDecalSetItemShaderPath(context, decalSetItem)
    313     if not shaderPath or not blue.paths.exists(shaderPath):
    314         return
    315     params, res, _ = GetMergedParameters(shaderPath)
    316     decalShader = _GetDecalSetItemShader(decalSetItem)
    317     for each in decalSetItem.parameters:
    318         if each.name not in params:
    319             context.Error(each, 'Parameter %s is not used by shader %s' % (each.name, decalShader))