########### VALVE FILESYSTEM INTEGRATION ###########
from path import Path, PathError
import os
import sys
### Platform
WIN_32_SUFFIX = 'win32'
WIN_64_SUFFIX = 'win64'
#make sure there is a HOME var...
try:
os.environ['HOME'] = os.environ['USERPROFILE']
except KeyError:
os.environ['HOME'] = str( Path( '%HOMEDRIVE%/%HOMEPATH%' ) )
_MOD = None
def mod():
'''
returns the mod name of the current project
'''
global _MOD
try:
_MOD = Path( os.environ[ 'VPROJECT' ] ).name()
return _MOD
except KeyError:
raise KeyError( '%VPROJECT% not defined' )
_GAME = None
def game():
'''
returns a Path instance representing the %VGAME% path - path construction this way is super easy:
somePropPath = game() / mod() / 'models/props/some_prop.dmx'
'''
global _GAME
try:
_GAME = Path.Join( os.environ[ 'VPROJECT' ], '..' )
return _GAME
except KeyError:
raise KeyError( '%VPROJECT% not defined.' )
except PathError:
raise PathError( '%VPROJECT% is defined with an invalid path.' )
_CONTENT = None
def content():
'''
returns a Path instance representing the %VCONTENT% path - path construction this way is super easy:
somePropPath = content() / 'ep3/models/characters/alyx/maya/alyx_model.ma'
'''
global _CONTENT
try:
return Path( os.environ[ 'VCONTENT' ] )
except KeyError:
try:
_CONTENT = Path.Join( os.environ[ 'VPROJECT' ], '../../content' )
return _CONTENT
except KeyError:
KeyError( '%VPROJECT% not defined' )
_PROJECT = None
def project():
'''
returns a Path instance representing the %VPROJECT% path - path construction this way is super easy:
somePropPath = project() / 'models/props/some_prop.mdl'
'''
global _PROJECT
try:
_PROJECT = Path( os.environ[ 'VPROJECT' ] )
return _PROJECT
except KeyError:
raise KeyError( '%VPROJECT% not defined' )
_TOOLS = None
def tools( engine='Source 2' ):
'''
returns the location of our tools.
'''
global _TOOLS
if engine == 'Source':
if _TOOLS is None:
try:
_TOOLS = Path(os.environ['VTOOLS'])
except KeyError:
try:
_TOOLS = Path.Join( os.environ[ 'VGAME' ], '/../../tools' )
except KeyError:
try:
_TOOLS = Path.Join( os.environ[ 'VPROJECT' ], '/../../../tools' )
except KeyError:
raise KeyError('%VGAME% or %VPROJECT% not defined - cannot determine tools path')
else:
if _TOOLS is None:
try:
_TOOLS = Path(os.environ['VTOOLS'])
except KeyError:
try:
_TOOLS = Path.Join( os.environ[ 'VGAME' ], '/sdktools' )
except KeyError:
try:
_TOOLS = Path.Join( os.environ[ 'VPROJECT' ], '../sdktools' )
except KeyError:
raise KeyError('%VGAME% or %VPROJECT% not defined - cannot determine tools path')
return _TOOLS
_PLATFORM = WIN_32_SUFFIX
def platform():
'''
Returns the platform of the current environment, defaults to win32
'''
global _PLATFORM
try:
_PLATFORM = os.environ[ 'VPLATFORM' ]
except KeyError:
try:
# next try to determine platform by looking for win64 bin directory in the path
bin64Dir = r'{0}\bin\{1}'.format( os.environ['VGAME'], WIN_64_SUFFIX )
if bin64Dir in os.environ['PATH']:
_PLATFORM = WIN_64_SUFFIX
except ( KeyError, PathError ):
pass
return _PLATFORM
def iterContentDirectories():
for m in gameInfo.getSearchMods():
yield content() / m
def iterGameDirectories():
for m in gameInfo.getSearchMods():
yield game() / m
def resolveValvePath( valvePath, basePath=content() ):
'''
A "Valve Path" is one that is relative to a mod - in either the game or content tree.
Ie: if you have a project with the content dir: d:/content and the game dir: d:/game
a "Valve Path" looks like this: models/awesomeProps/coolThing.vmdl
To resolve this path one must look under each mod the current project inherits from.
So a project "test" which inherits from "another" would result in the following searches:
d:/content/test/models/awesomeProps/coolThing.vmdl
d:/content/another/models/awesomeProps/coolThing.vmdl
Similarly for the game tree.
If the path cannot be resolved to a real file, None is returned.
'''
for mod in gameInfo.getSearchMods():
p = basePath / mod / valvePath
if p.exists:
return p
def setMod( newMod ):
'''
sets the current mod to something else. makes sure to update VPROJECT, VMOD and re-parses global gameInfo for
the new mod so that calls to gamePath and contentPath return correctly
'''
global gameInfo
os.environ[ 'VMOD' ] = str( newMod )
os.environ[ 'VPROJECT' ] = (game() / newMod).asNative()
gameInfo = GameInfoFile()
def reportUsageToAuthor( author=None, payloadCB=None ):
'''
when called, this method will fire of a useage report email to whoever has marked themselves as the __author__ of the tool
the call was made from. if no author is found then an email is sent to the DEFAULT_AUTHOR
'''
#additionalMsg = ''
#try:
#additionalMsg = payloadCB()
#except: pass
#try:
#fr = inspect.currentframe()
#frameInfos = inspect.getouterframes( fr, 0 )
#dataToSend = []
#if author is None:
##set the default - in case we can't find an __author__ variable up the tree...
#author = DEFAULT_AUTHOR
##in this case, walk up the caller tree and find the top most __author__ variable definition
#for frameInfo in frameInfos:
#frame = frameInfo[0]
#dataToSend.append( '%s: %s' % (Path( frameInfo[1] ) - '%VTOOLS%', frameInfo[3]) )
#if author is None:
#try:
#author = frame.f_globals['__author__']
#except KeyError: pass
#try:
#author = frame.f_locals['__author__']
#except KeyError: pass
#import smtplib
#envDump = '\ncontent: %s\nproject: %s\n' % (content(), project())
#subject = '[using] %s' % str( Path( frameInfos[1][1] ).name() )
#msg = u'Subject: %s\n\n%s\n\n%s\n\n%s' % (subject, '\n'.join( map(str, dataToSend) ), envDump, additionalMsg)
#def sendMail():
#try:
#svr = smtplib.SMTP( MAIL_SERVER )
#svr.sendmail( '%s@valvesoftware.com' % os.environ[ 'USERNAME' ], author, msg )
#except: pass
##throw the mail sending into a separate thread in case the mail server is being tardy - if it succeeds, great, if something happens, meh...
#threading.Thread( target=sendMail ).start()
##NOTE: this method should never ever throw an exception... its purely a useage tracking tool and if it fails, it should fail invisibly...
#except: pass
pass
def asRelative( path ):
'''
'''
return str( Path(path).asRelative() )
def contentModRelativePath( path ):
'''
returns a path instance that is relative to the mod if the path is under the content tree
'''
return (path - content())[1:]
def projectRelativePath( path ):
'''
returns a path instance that is relative to vproject(). this method is provided purely for symmetry - its pretty trivial
'''
return path - project()
def makeSourceAbsolutePath( filepath ):
'''
Returns a Path instance as a "source" absolute filepath.
If the filepath doesn't exist under the project tree, the original filepath is returned.
'''
if filepath.isUnder( content() ):
relative = filepath - content()
elif filepath.isUnder( game() ):
relative = filepath - game()
else:
return filepath
relToCurMod = relative[ 1: ]
return relToCurMod
def makeSource1TexturePath( thePath ):
'''
returns the path as if it were a source1 texture/material path - ie the path is relative to the
materials, or materialsrc directory. if the materials or materialsrc directory can't be found
in the path, the original path is returned
'''
if not isinstance( thePath, Path ):
thePath = Path( thePath )
try:
idx = thePath.index( 'materials' )
except ValueError:
try:
idx = thePath.index( 'materialsrc' )
except ValueError:
return thePath
return thePath[ idx+1: ]
########### DEPENDENCY CHECKING ###########
_VALIDATE_LOCATION_IMPORT_HOOK = None
def EnableValidDependencyCheck():
'''
sets up an import hook that ensures all imported modules live under the game directory
'''
global _VALIDATE_LOCATION_IMPORT_HOOK
validPaths = [ game() ]
class Importer(object):
def find_module( self, fullname, path=None ):
lastName = fullname.rsplit( '.', 1 )[ -1 ]
scriptName = lastName +'.py'
if path is None:
path = []
#not sure if there is a better way of asking python where it would look for a script/module - but I think this encapsulates the logic...
#at least under 2.6. I think > 3 works differently?
for d in (path + sys.path):
pyFilepath = Path( d ) / scriptName
pyModulePath = Path( d ) / lastName / '__init__.py'
if pyFilepath.exists or pyModulePath.exists:
for validPath in validPaths:
if pyFilepath.isUnder( validPath ):
return None
print "### importing a script outside of game!", pyFilepath
return None
return None
_VALIDATE_LOCATION_IMPORT_HOOK = Importer()
sys.meta_path.append( _VALIDATE_LOCATION_IMPORT_HOOK )
def DisableValidDependencyCheck():
'''
disables the location validation import hook
'''
global _VALIDATE_LOCATION_IMPORT_HOOK
if _VALIDATE_LOCATION_IMPORT_HOOK is None:
return
sys.meta_path.remove( _VALIDATE_LOCATION_IMPORT_HOOK )
_VALIDATE_LOCATION_IMPORT_HOOK = None
try:
enableHook = os.environ[ 'ENSURE_PYTHON_CONTAINED' ]
if enableHook: EnableValidDependencyCheck()
except KeyError:
pass
########### VALVE SPECIFIC PATH METHODS ###########
def expandAsGame( self, gameInfo, extension=None ):
'''
expands a given "mod" relative path to a real path under the game tree. if an extension is not given, it is
assumed the path already contains an extension. ie: models/player/scout.vmt would get expanded to:
<gameRoot>/<mod found under>/models/player/scout.vmt - where the mod found under is the actual mod the file
exists under on the user's system
'''
thePath = self
if extension is not None:
thePath = self.setExtension( extension )
searchPaths = gameInfo.getSearchPaths()
for path in searchPaths:
tmp = path / thePath
if tmp.exists:
return tmp
return None
def expandAsContent( self, gameInfo, extension=None ):
'''
as for expandAsGame except for content rooted paths
'''
thePath = self
if extension is not None:
thePath = self.setExtension( extension )
searchMods = gameInfo.getSearchMods()
for mod in searchMods:
underMod = '%VCONTENT%' / (mod / thePath)
if underMod.exists:
return underMod
return None
def belongsToContent( self, gameInfo ):
for mod in gameInfo.getSearchMods():
if self.isUnder( content() / mod ):
return True
return False
def belongsToGame( self, gameInfo ):
for mod in gameInfo.getSearchMods():
if self.isUnder( game() / mod ):
return True
return False
def asRelative( self ):
'''
returns the path relative to either VCONTENT or VGAME. if the path isn't under either of these directories
None is returned
'''
c = content()
g = game()
if self.isUnder( c ):
return self - c
elif self.isUnder( g ):
return self - g
return None
Path.expandAsGame = expandAsGame
Path.expandAsContent = expandAsContent
Path.belongsToContent = belongsToContent
Path.belongsToGame = belongsToGame
Path.asRelative = asRelative
########### VALVE KEYVALUES PARSER ###########
def removeLineComments( lines ):
'''
removes all line comments from a list of lines
'''
newLines = []
for line in lines:
commentStart = line.find('//')
if commentStart != -1:
line = line[:commentStart]
if not line: continue
# strip trailing whitespace and tabs
line = line.rstrip( ' \t' )
newLines.append(line)
return newLines
def removeBlockComments( lines ):
'''
removes all block comments from a list of lines
'''
newLines = []
end = len(lines)
n = 0
while n<end:
blockCommentStart = lines[n].find('/*')
newLines.append(lines[n])
contFlag = 0
if blockCommentStart != -1:
newLines[-1] = lines[n][:blockCommentStart]
while n<end:
blockCommentEnd = lines[n].find('*/')
if blockCommentEnd != -1:
newLines[-1] += lines[n][blockCommentEnd+2:]
n+=1
contFlag = 1
break
n+=1
if contFlag: continue
n+=1
return newLines
class Chunk( object ):
'''
a chunk creates a reasonably convenient way to hold and access key value pairs, as well as a way to access
a chunk's parent. the value attribute can contain either a string or a list containing other Chunk instances
'''
def __init__( self, key, value=None, parent=None, append=False, quoteCompoundKeys=True ):
self.key = key
self.value = value
self.parent = parent
self.quoteCompoundKeys = quoteCompoundKeys
if append:
parent.append(self)
def __getitem__( self, item ):
return self.value[item]
def __getattr__( self, attr ):
if self.hasLen:
for val in self.value:
if val.key == attr:
return val
raise AttributeError( "has no attribute called %s"%attr )
def __len__( self ):
if self.hasLen: return len(self.value)
return None
def _hasLen( self ):
return isinstance( self.value, list )
hasLen = property(_hasLen)
def __iter__( self ):
if self.hasLen:
return iter(self.value)
raise TypeError( "non-compound value is not iterable" )
def __repr__( self, depth=0 ):
strLines = []
compoundLine = '{0}{1}\n'
if self.quoteCompoundKeys:
compoundLine = '{0}"{1}"\n'
if isinstance( self.value,list ):
strLines.append( compoundLine.format( '\t'*depth, self.key ) )
strLines.append( '\t'*depth +'{\n' )
for val in self.value: strLines.append( val.__repr__( depth+1 ) )
strLines.append( '\t'*depth +'}\n' )
else:
v = self.value
strLines.append( '%s"%s" "%s"\n'%('\t'*depth, self.key, v) )
return ''.join( strLines )
__str__ = __repr__
def __hash__( self ):
return id( self )
def iterChildren( self ):
'''
'''
if self.hasLen:
for chunk in self:
if chunk.hasLen:
for subChunk in chunk.iterChildren():
yield subChunk
else:
yield chunk
def asDict( self, parentDict ):
if isinstance(self.value, list):
parentDict[self.key] = subDict = {}
for c in self.value:
c.asDict(subDict)
else:
parentDict[self.key] = self.value
def append( self, new ):
'''
Append a chunk to the end of the list.
'''
if not isinstance( self.value, list ):
self.value = []
self.value.append( new )
#set the parent of the new Chunk to this instance
new.parent = self
def insert( self, index, new ):
'''
Insert a new chunk at a particular index.
'''
if not isinstance( self.value, list ):
self.value = []
self.value.insert( index, new )
# Set the parent of the new Chunk to this instance
new.parent = self
def findKey( self, key ):
'''
recursively searches this chunk and its children and returns a list of chunks with the given key
'''
matches = []
if self.key == key:
matches.append(self)
if self.hasLen:
for val in self.value:
matches.extend(val.findKey(key))
return matches
def findValue( self, value ):
'''
recursively searches this chunk and its children and returns a list of chunks with the given value
'''
matches = []
if self.hasLen:
for val in self.value:
matches.extend(val.findValue(value))
elif self.value == value:
matches.append(self)
return matches
def findKeyValue( self, key, value ):
'''
recursively searches this chunk and its children and returns a list of chunks with the given key AND value
'''
matches = []
if self.hasLen:
for val in self.value:
matches.extend(val.findKeyValue(key,value))
elif self.key == key and self.value == value:
matches.append(self)
return matches
def testOnValues( self, valueTest ):
matches = []
if self.hasLen:
for val in self.value:
matches.extend( val.testOnValues(valueTest) )
elif valueTest(self.value):
matches.append(self)
return matches
def listAttr( self ):
#lists all the "attributes" - an attribute is just as a named key. NOTE: only Chunks with length have attributes
attrs = []
for attr in self:
attrs.append(attr.key)
return attrs
def hasAttr( self, attr ):
attrs = self.listAttr()
return attr in attrs
def getFileObject( self ):
'''
walks up the chunk hierarchy to find the file to which this chunk instance belongs
'''
parent = self.parent
lastParent = parent
safety = 1000
while parent is not None and safety:
lastParent = parent
parent = parent.parent
safety -= 1
return lastParent
def parseLine( line ):
'''
this line parser works well for all internal key value files I've thrown at it
'''
c = line.count('"')
if c == 4:
toks = line[1:-1].split('"')
return toks[0],toks[-1]
if c == 2:
#so this could mean we have a
#A) "key" value OR
#B) key "value" OR
#C) "key"
if line.strip().startswith('"'):
toks = [tok.strip() for tok in line[1:].split('"')]
tokCount = len(toks)
if tokCount == 2:
return toks[0],toks[-1].strip()
elif tokCount == 1:
return toks[0],[]
else:
toks = [tok.strip() for tok in line.rstrip()[:-1].split('"')]
#toks = line.split()
#idx = line.find('"')
#value = line[idx:].strip()[1:-1]
tokCount = len(toks)
if tokCount == 2:
return toks[0],toks[-1].strip()
elif tokCount == 1:
return toks[0],[]
elif tokCount > 2:
key = toks[0]
for v in toks[1:]:
if v.strip() != '':
return key, v
if c == 0:
toks = line.split()
tokCount = len(toks)
if tokCount == 2: return toks[0],toks[1]
if tokCount == 1: return toks[0],[]
class KeyValueFile( object ):
'''
self.data contains a list which holds all the top level Chunk objects
'''
def __init__( self, filepath=None, lineParser=parseLine, chunkClass=Chunk, readCallback=None, supportsComments=True ):
'''
lineParser needs to return key,value
'''
self.filepath = filepath
self.data = self.value = []
self.key = None
self.parent = None
self.lineParser = lineParser
self.chunkClass = chunkClass
self.callback = readCallback
self.supportsComments = supportsComments
def nullCallback(*args): pass
self.nullCallback = nullCallback
#if no callback is defined, create a dummy one
if self.callback is None:
self.callback = nullCallback
#if no line parser was given, then use a default one
if self.lineParser is None:
def simpleLineParse( line ):
toks = line.split()
if len(toks) == 1: return toks[0],[]
else: return toks[0],toks[1]
self.lineParser = simpleLineParse
#if a filepath exists, then read it
if self.filepath.exists:
self.read()
def getFilepath( self ):
return self._filepath
def setFilepath( self, newFilepath ):
'''
this wrapper is here so to ensure the _filepath attribute is a Path instance
'''
self._filepath = Path(newFilepath)
filepath = property(getFilepath, setFilepath)
def read( self, filepath=None ):
'''
reads the actual file, and passes the data read over to the parseLines method
'''
if filepath == None: filepath = self.filepath
else: filepath = Path(filepath)
self.parseLines( filepath.read() )
def parseLines( self, lines ):
'''
this method does the actual parsing/data creation. deals with comments, passing off data to the lineParser,
firing off the read callback, all that juicy stuff...
'''
lines = [l.strip() for l in lines]
#remove comments
if self.supportsComments:
lines = removeLineComments(lines)
lines = removeBlockComments(lines)
numLines = len(lines)
#hold a list representation of the current spot in the hierarchy
parentList = [self]
parentListEnd = self
callback = self.callback
lineParser = self.lineParser
n = 0
for line in lines:
#run the callback - if there are any problems, replace the callback with the nullCallback
try: callback(n,numLines)
except: callback = self.nullCallback
if line == '': pass
elif line == '{':
curParent = parentList[-1][-1]
parentList.append(curParent)
parentListEnd = curParent
elif line == '}':
parentList.pop()
parentListEnd = parentList[-1]
else:
try:
key, value = lineParser(line)
except TypeError:
raise TypeError( 'Malformed kevalue found in file near line {0}'.format( n+1 ) )
parentListEnd.append( self.chunkClass(key, value, parentListEnd) )
n += 1
def __getitem__( self, *args ):
'''
provides an index based way of accessing file data - self[0,1,2] accesses the third child of
the second child of the first root element in self
'''
args = args[0]
if not isinstance(args,tuple):
data = self.data[args]
else:
data = self.data[args[0]]
if len(args) > 1:
for arg in args[1:]:
data = data[arg]
return data
def __len__( self ):
'''
lists the number of root elements in the file
'''
return len(self.data)
def __repr__( self ):
'''
this string representation of the file is almost identical to the formatting of a vmf file written
directly out of hammer
'''
strList = []
for chunk in self.data:
strList.append( str(chunk) )
return ''.join(strList)
__str__ = __repr__
serialize = __repr__
def unserialize( self, theString ):
'''
'''
theStringLines = theString.split( '\n' )
self.parseLines( theStringLines )
@property
def hasLen( self ):
try:
self.data[ 0 ]
return True
except IndexError:
return False
def asDict( self ):
'''
returns a dictionary representing the key value file - this isn't always possible as it is valid for
a keyValueFile to have mutiple keys with the same key name within the same level - which obviously
isn't possible with a dictionary - so beware!
'''
asDict = {}
for chunk in self.data:
chunk.asDict( asDict )
return asDict
def append( self, chunk ):
'''
appends data to the root level of this file - provided to make the vmf file object appear
more like a chunk object
'''
self.data.append( chunk )
def findKey( self, key ):
'''
returns a list of all chunks that contain the exact key given
'''
matches = []
for item in self.data:
matches.extend( item.findKey(key) )
return matches
def findValue( self, value ):
'''
returns a list of all chunks that contain the exact value given
'''
matches = []
for item in self.data:
matches.extend( item.findValue(value) )
return matches
def findKeyValue( self, key, value ):
'''
returns a list of all chunks that have the exact key and value given
'''
matches = []
for item in self.data:
matches.extend( item.findKeyValue(key,value) )
return matches
def testOnValues( self, valueTest ):
'''
returns a list of chunks that return true to the method given - the method should take as its
first argument the value of the chunk it is testing against. can be useful for finding values
containing substrings, or all compound chunks etc...
'''
matches = []
for item in self.data:
matches.extend( item.testOnValues(valueTest) )
return matches
def write( self, filepath=None, doP4=True ):
'''
writes the instance back to disk - optionally to a different location from that which it was
loaded. NOTE: deals with perforce should the file be managed by p4
'''
if filepath is None:
filepath = self.filepath
else:
filepath = Path(filepath)
filepath.write(str(self), doP4=doP4)
def resetCache( self ):
pass
class GameInfoFile( KeyValueFile ):
'''
Provides an interface to gameinfo relevant operations - querying search paths, game root, game title etc...
'''
def __init__( self, filepath=None, chunkClass=Chunk, readCallback=None ):
try:
project()
except: return
if filepath is None:
filepath = project() / 'gameinfo.gi'
# look for a gameinfo.txt instead, pick a gameinfo.gi as default if it doesn't exist.
if not filepath.exists:
filepath = project() / 'gameinfo.txt'
if not filepath.exists:
filepath = project() / 'gameinfo.gi'
if filepath:
self.filename = filepath.split()[ - 1 ]
else:
self.filename = None
KeyValueFile.__init__(self, filepath, parseLine, chunkClass, readCallback, True)
def __getattr__( self, attr ):
return getattr(self[0],attr)
def getSearchPaths( self ):
return [ Path.Join( '%VPROJECT%/../', mod ) for mod in self.getSearchMods() ]
def getSearchMods( self ):
#always has the base mod in it...
searchPaths = [mod()]
pathsChunk = self.FileSystem.SearchPaths
paths = [chunk.value for chunk in pathsChunk]
gi = '|gameinfo_path|'
sp = '|all_source_engine_paths|'
for path in paths:
pos = path.find(gi)
if pos != -1: continue
path = path.replace(sp,'')
if not path: continue
if path not in searchPaths: searchPaths.append( path )
return searchPaths
def getTitle( self ):
try:
return self[0].title.value
except AttributeError:
try:
return self[0].game.value
except:
return "unknown"
title = property(getTitle)
def getEngine( self ):
try:
return self[0].ToolsEnvironment.Engine.value
except AttributeError:
try:
return self[0].engine.value
except:
return "unknown"
engine = property( getEngine )
def getToolsDir( self ):
try:
return self[0].ToolsEnvironment.ToolsDir.value
except AttributeError:
try:
return self[0].ToolsDir.value
except:
return "unknown"
toolsDir = property( getToolsDir )
def writeDefaultFile( self ):
'''
Creates a default GameInfo file with basic structure
'''
self.filepath.write( '''"GameInfo"\n{\n\tgame "tmp"\n\tFileSystem\n\t{\n\t\tSearchPaths\n'+
'\t\t{\n\t\t\tGame |gameinfo_path|.\n\t\t}\n\t}\n}''' )
def simpleValidate( self ):
'''
Checks to see if the file has some basic keyvalues
'''
try:
getattr( self[0], 'game' )
getattr( self[0], 'SearchPaths' )
return True
except AttributeError:
raise GameInfoException( 'Not a valid gameinfo file.' )
# Read the current gameinfo
gameInfo = GameInfoFile()
class GameInfoException(Exception):
pass
def lsGamePath( path, recursive=False ):
'''
lists all files under a given 'valve path' - ie a game or content relative path. this method needs to iterate
over all known search mods as defined in a project's gameInfo script
'''
path = Path( path )
files = []
for modPath in [ base / path for base in gameInfo.getSearchPaths() ]:
files += list( modPath.files( recursive=recursive ) )
return files
def lsContentPath( path, recursive=False ):
'''
similar to lsGamePath except that it lists files under the content tree, not the game tree
'''
path = Path( path )
c = content()
files = []
for modPath in [ c / mod / path for mod in gameInfo.getSearchMods() ]:
files += list( modPath.files( recursive=recursive ) )
return files
def contentPath( modRelativeContentPath ):
'''allows you do specify a path using mod relative syntax instead of a fullpath
example:
assuming vproject is set to d:/main/game/tf_movies
contentPath( 'models/player/soldier/parts/maya/soldier_reference.ma' )
returns: d:/main/content/tf/models/player/soldier/parts/maya/soldier_reference.ma
NOTE: None is returned in the file can't be found in the mod hierarchy
'''
return Path( modRelativeContentPath ).expandAsContent( gameInfo )
def gamePath( modRelativeContentPath ):
'''allows you do specify a path using mod relative syntax instead of a fullpath
example:
assuming vproject is set to d:/main/game/tf
gamePath( 'models/player/soldier.mdl' )
returns: d:/main/game/tf/models/player/soldier.mdl
NOTE: None is returned in the file can't be found in the mod hierarchy
'''
return Path( modRelativeContentPath ).expandAsGame( gameInfo )
def textureAsGameTexture( texturePath ):
'''
returns a resolved game texture filepath given some sort of texture path
'''
if not isinstance( texturePath, Path ):
texturePath = Path( texturePath )
if texturePath.isAbs():
if texturePath.isUnder( content() ): relPath = texturePath - content()
else: relPath = texturePath - game()
relPath = relPath[ 2: ]
else:
relPath = texturePath
if relPath.startswith( 'materials' ) or relPath.startswith( 'materialsrc' ):
relPath = relPath[ 1: ]
relPath = ('materials' / relPath).setExtension( 'vtf' )
relPath = relPath.expandAsGame( gameInfo )
return relPath
def textureAsContentTexture( texturePath ):
'''
returns a resolved content texture filepath for some sort of texture path. it looks for psd
first, and then for tga. if neither are found None is returned
'''
if not isinstance( texturePath, Path ):
texturePath = Path( texturePath )
xtns = [ 'psd', 'tga' ]
if texturePath.isAbs():
if texturePath.isUnder( content() ): relPath = texturePath - content()
else: relPath = texturePath - game()
relPath = relPath[ 2: ]
else:
relPath = texturePath
if relPath.startswith( 'materials' ) or relPath.startswith( 'materialsrc' ):
relPath = relPath[ 1: ]
contentPath = 'materialsrc' / relPath
for xtn in xtns:
tmp = contentPath.expandAsContent( gameInfo, xtn )
if tmp is None: continue
return tmp
return None
def resolveMaterialPath( materialPath ):
'''
returns a resolved material path given some sort of material path
'''
if not isinstance( materialPath, Path ):
materialPath = Path( materialPath )
if materialPath.isAbs():
if materialPath.isUnder( content() ): relPath = materialPath - content()
else: relPath = materialPath - game()
relPath = relPath[ 2: ]
else:
relPath = materialPath
if relPath.startswith( 'materials' ) or relPath.startswith( 'materialsrc' ):
relPath = relPath[ 1: ]
relPath = ('materials' / relPath).setExtension( 'vmt' )
relPath = relPath.expandAsGame( gameInfo )
return relPath
#end