442 lines
17 KiB
Ruby
Executable File
442 lines
17 KiB
Ruby
Executable File
#
|
|
# File:: max_wildwest_regen.rb
|
|
# Description:: Generates the wildwest menu in max based on the contents of:
|
|
# ../tools/wildwest/script/max/
|
|
# looping through each studio's wildwest folder, using an xml config file
|
|
# to filter files or override button tooltips etc
|
|
#
|
|
# Author:: Adam Munson <adam.munson@rockstarnorth.com>
|
|
# Date:: 29 November 2010
|
|
#
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'pipeline/config/projects'
|
|
require 'pipeline/os/path'
|
|
require 'pipeline/os/file'
|
|
require 'pipeline/os/getopt'
|
|
require 'pipeline/util/maxscript'
|
|
require 'pipeline/scm/perforce'
|
|
require 'rexml/document'
|
|
|
|
include Pipeline
|
|
include REXML
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Constants
|
|
#-----------------------------------------------------------------------------
|
|
OPTIONS = [
|
|
[ '--wildwest', '-w', OS::Getopt::REQUIRED, 'wildwest directory' ],
|
|
[ '--help', '-h', OS::Getopt::BOOLEAN, 'display usage information' ]
|
|
]
|
|
|
|
#
|
|
# This takes a menu name from the folder structure and formats it to have
|
|
# capitals at the start of each word, as well as replacing underscores with
|
|
# spaces.
|
|
#
|
|
# e.g formatName("rockstar_north") => "Rockstar North"
|
|
#
|
|
def formatName(n)
|
|
|
|
if(n.include?('_')) then
|
|
newName = (n.gsub('_',' ').split(' ').each do |part| part.capitalize! end ).join(" ")
|
|
elsif(n.include?(' ')) then
|
|
newName = (n.split(' ').each do |part| part.capitalize! end ).join(" ")
|
|
else
|
|
newName = n.capitalize
|
|
end
|
|
newName
|
|
end
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Implementation
|
|
#-----------------------------------------------------------------------------
|
|
if(__FILE__ == $0) then
|
|
|
|
begin
|
|
g_AppName = OS::Path::get_basename( __FILE__ )
|
|
g_Log = Log.new( g_AppName )
|
|
g_Config = Pipeline::Config::instance( )
|
|
g_Errors = []
|
|
|
|
#---------------------------------------------------------------------
|
|
# Parse Command Line
|
|
#---------------------------------------------------------------------
|
|
g_Opts, g_Trailing = OS::Getopt::getopts( OPTIONS )
|
|
|
|
if ( g_Opts['help'] ) then
|
|
puts OS::Getopt::usage( OPTIONS )
|
|
exit( 1 )
|
|
end
|
|
|
|
wildDir = ( nil == g_Opts['wildwest'] ) ? '' : g_Opts['wildwest']
|
|
unless ( ::File::directory?( wildDir ) ) then
|
|
puts OS::Getopt::usage( OPTIONS )
|
|
g_Log.error( "Unable to find Wildwest directory #{wildDir}." )
|
|
exit( 2 )
|
|
end
|
|
wildDir = OS::Path::normalise(wildDir)
|
|
g_Log.info( "Wildwest directory: #{wildDir}" )
|
|
|
|
#---------------------------------------------------------------------
|
|
# Perforce connection and file checkout
|
|
#---------------------------------------------------------------------
|
|
# Set up the Perforce connection
|
|
con = Pipeline::Config.instance
|
|
p4 = SCM::Perforce::create( con.sc_tools_server, con.sc_tools_username, con.sc_tools_workspace )
|
|
p4.connect unless p4.connected?
|
|
g_Log.info( "Perforce Server: #{con.sc_tools_server}." )
|
|
|
|
# Create the changelist
|
|
change_id = p4.create_changelist( "Auto-regeneration of RS Wildwest" )
|
|
|
|
p4.run_sync_with_block( "#{OS::Path::get_directory( wildDir )}/..." ) do |filename|
|
|
g_Log.debug( "\t#{filename}" )
|
|
end
|
|
|
|
# Get the installed versions of max so we can run the generation for
|
|
# multiple versions. Started doing this with Max 2011 and 2012, so ignore
|
|
# any versions below Max 2011 (version 13.0)
|
|
autoDesk3dsMax = Autodesk3dsmax::instance()
|
|
maxVersions = autoDesk3dsMax.versions()
|
|
maxVersions.delete_if {|v| v.to_f < 13.0 }
|
|
|
|
maxVersions.each do |version|
|
|
|
|
maxRelPath = ('dcc/current/' + Autodesk3dsmax::VERSION_DIRS[version.to_f])
|
|
maxPath = OS::Path::combine(con.toolsroot, maxRelPath)
|
|
|
|
mcrPath = OS::Path::combine( maxPath, "UI/MacroScripts/rswildwest.mcr" )
|
|
msPath = OS::Path::combine( (maxPath + '/scripts'), "pipeline/rswildwest.ms" )
|
|
|
|
# Get latest revision
|
|
g_Log.info( "Getting latest revision..." )
|
|
p4.run_sync_with_block( msPath, mcrPath ) do |filename|
|
|
g_Log.debug( "\t#{filename}" )
|
|
end
|
|
|
|
# Check out files and add new files
|
|
g_Log.info( "Checking out Wildwest files..." )
|
|
p4.run_edit_or_add( '-c', change_id.to_s, msPath, mcrPath )
|
|
|
|
# Open the output macro file and write the header
|
|
::FileUtils::rm( mcrPath ) if ( File.exist?( mcrPath ) )
|
|
wildMcr = File.new( mcrPath, 'w' )
|
|
wildMcr.write( "--\n" )
|
|
wildMcr.write( "-- Description:: RS Wildwest Macros\n" )
|
|
wildMcr.write( "--\n" )
|
|
wildMcr.write( "-- Auto-generated by tools/util/max/max_wildwest_menu.rb\n" )
|
|
wildMcr.write( "-- #{Date.today.to_s}\n" )
|
|
wildMcr.write( "--\n" )
|
|
wildMcr.write("include \"rockstar/export/settings.ms\"\n")
|
|
|
|
wildMcr.write("\n-- Wildwest help link\n")
|
|
wildMcr.write("macroscript wildwestHelp\n")
|
|
wildMcr.write("\tcategory:\"RS Wildwest\"\n")
|
|
wildMcr.write("\tButtonText:\"Wildwest Regeneration Help\"\n")
|
|
wildMcr.write("(\n\tshellLaunch \"https://devstar.rockstargames.com/wiki/index.php/Wildwest_Tutorial\" \"\"\n)\n")
|
|
|
|
# Open the maxscript file and write the header
|
|
::FileUtils::rm( msPath ) if ( File.exist?( msPath ) )
|
|
wildMs = File.new( msPath, 'w' )
|
|
wildMs.write( "--\n" )
|
|
wildMs.write( "-- Description:: RS Wildwest Menu\n" )
|
|
wildMs.write( "--\n" )
|
|
wildMs.write( "-- Auto-generated by tools/util/max/max_wildwest_regen.rb\n" )
|
|
wildMs.write( "-- #{Date.today.to_s}\n" )
|
|
wildMs.write( "--\n" )
|
|
wildMs.write("\nRsSetMenu \"RS Wildwest\" #(\"wildwestHelp\")\n")
|
|
|
|
# Struct for a file that is specified in config file to be overridden
|
|
attributeStruct = Struct.new(:file, :toolTip, :buttonText, :icon, :maxscriptBody)
|
|
# Struct for each menu and the scripts that are to be placed on it
|
|
menuStruct = Struct.new(:name, :parent, :items, :nameFormatted, :parentFormatted)
|
|
|
|
studioMenus = OS::FindEx::find_dirs( wildDir )
|
|
studioMenus.each do |studioWildwestDir|
|
|
|
|
# Get the name of the studio folder
|
|
studioName = formatName( OS::Path::get_directories(studioWildwestDir).pop )
|
|
g_Log.info(studioName + " wildwest folder.")
|
|
|
|
#Should only find one file
|
|
configFiles = OS::FindEx::find_files( OS::Path::combine( studioWildwestDir, 'wildwestconfig.xml' ) )
|
|
|
|
if ( configFiles.length > 1 ) then
|
|
g_Log.error( "Multiple Wildwest config files found in folder for " + studioName )
|
|
exit ( 3 )
|
|
elsif (configFiles.length == 0 ) then
|
|
errMsg = ("Wildwest config file not found for " + studioName + " so everything is included")
|
|
g_Log.error( errMsg )
|
|
g_Errors << errMsg
|
|
else
|
|
configFile = configFiles[0]
|
|
g_Log.info("Found config file: " + configFile)
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Read XML files/folders to be ignored
|
|
#---------------------------------------------------------------------
|
|
patternsToIgnore = []
|
|
if ( configFile ) then
|
|
|
|
configDoc = Document.new File.new(configFile)
|
|
XPath.each( configDoc, "//config/excludeItems/item" ) {
|
|
|element| patternsToIgnore << (OS::Path::combine( studioWildwestDir, element.text ))
|
|
}
|
|
end
|
|
|
|
# Ignore any old menu.ms files incase they're kicking about
|
|
patternsToIgnore << "*menu.ms"
|
|
|
|
# Make sure no duplicates just incase
|
|
patternsToIgnore.uniq
|
|
g_Log.debug("Patterns to ignore:" + patternsToIgnore.to_s)
|
|
|
|
#---------------------------------------------------------------------
|
|
# Filter files and folders
|
|
#---------------------------------------------------------------------
|
|
studioWildwestFiles = OS::FindEx::find_files_recurse( OS::Path::combine(studioWildwestDir, '*.*' ) )
|
|
|
|
filesToIgnore = []
|
|
# Check type of file and match against specified patterns to ignore against all files in
|
|
# the studio's wildwest folder.
|
|
studioWildwestFiles.each do |filename|
|
|
|
|
# Delete anything that isn't a maxscript file then move to next file in list
|
|
unless ( "ms" == OS::Path::get_extension( filename ) ) then
|
|
|
|
filesToIgnore << filename
|
|
next
|
|
end
|
|
|
|
patternsToIgnore.each do |pattern|
|
|
|
|
# Check if file matches a given pattern and add to delete list
|
|
# if it does, then stop checking patterns when first match is found
|
|
if ( ::File.fnmatch( pattern, filename) == true ) then
|
|
|
|
filesToIgnore << filename
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
# Remove files
|
|
if ( filesToIgnore.length > 0 ) then
|
|
|
|
g_Log.debug("Files ignored (pattern matched): " )
|
|
filesToIgnore.each do |f|
|
|
|
|
g_Log.debug(f)
|
|
studioWildwestFiles.delete( f )
|
|
end
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Check for files to override with attributes from config
|
|
#---------------------------------------------------------------------
|
|
macroscriptOverrides = []
|
|
|
|
# This list stores the overridden menu items to be removed later, before
|
|
# we write to the mcr file.
|
|
filesToRemove = []
|
|
|
|
# Check for any overrides for attributes in the wildwestconfig file
|
|
if ( configDoc ) then
|
|
|
|
XPath.each( configDoc, "//config/attributes/macroscript" ) do |element|
|
|
|
|
newStruct = attributeStruct.new()
|
|
relFilePath = element.attribute('file')
|
|
if ( relFilePath.nil? or '' == relFilePath.to_s) then
|
|
errMsg = "No file attribute given in config file for an override - it won't appear on the menu "
|
|
g_Log.error(errMsg)
|
|
g_Errors << errMsg
|
|
else
|
|
newStruct.file = (OS::Path::combine( studioWildwestDir, relFilePath.to_s))
|
|
# Add to wildwest files to make sure the menu structure is created
|
|
studioWildwestFiles << newStruct.file
|
|
|
|
newStruct.toolTip = element.attribute('toolTip')
|
|
newStruct.buttonText = element.attribute('buttonText')
|
|
newStruct.icon = element.attribute('icon')
|
|
newStruct.maxscriptBody = element.attribute('maxscriptBody')
|
|
macroscriptOverrides << newStruct
|
|
filesToRemove << newStruct.file
|
|
end
|
|
end
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Get menu structure from files and directories
|
|
#---------------------------------------------------------------------
|
|
menus = []
|
|
studioWildwestFiles.each do |f|
|
|
|
|
# Splits up the file path to get each directory name below the
|
|
# studio wildwest folder, then gets the parent and makes sure it's not
|
|
# already been added to the menu list already.
|
|
dirs = ( OS::Path::get_directories( f.gsub(wildDir, '') ) )
|
|
for i in 0..(dirs.length-1)
|
|
next unless dirs[i] != ''
|
|
dirFormatted = formatName(dirs[i])
|
|
menu = menuStruct.new(dirs[i], '', [], dirFormatted, '')
|
|
if dirFormatted == studioName or dirFormatted == '' then
|
|
menu.parent = "RS Wildwest"
|
|
menu.parentFormatted = "RS Wildwest"
|
|
else
|
|
menu.parent = dirs[i - 1]
|
|
menu.parentFormatted = formatName(dirs[i-1])
|
|
end
|
|
menus << menu unless menus.include?( menu )
|
|
end
|
|
end
|
|
|
|
# Take a copy of the array so we can delete items from the main
|
|
# array when looping
|
|
studioFilesCopy = Array.new(studioWildwestFiles)
|
|
menus.each do |menu|
|
|
|
|
(studioWildwestFiles.length-1).downto(0) {
|
|
|
|
|i| fileMenuName = OS::Path.get_trailing_directory( studioWildwestFiles[i] )
|
|
if( fileMenuName == menu.name ) then
|
|
if ( menu.parent != "RS Wildwest" ) then
|
|
next unless studioWildwestFiles[i].include?(menu.parent)
|
|
end
|
|
scriptName = OS::Path.get_basename(studioWildwestFiles[i])
|
|
# If the script name contains spaces, it won't work in the macroscript so ignore
|
|
# it and remove it from the copied array so it won't be used later
|
|
if ( scriptName.include?(' ') )then
|
|
errMsg = "A script contains space characters which isn't allowed in macroscript, so it was ignored (#{ studioWildwestFiles[i] } )"
|
|
g_Log.error( errMsg )
|
|
g_Errors << errMsg
|
|
studioFilesCopy.delete_at( i )
|
|
else
|
|
menu.items << scriptName unless menu.items.include?(scriptName)
|
|
end
|
|
studioWildwestFiles.delete_at( i )
|
|
end
|
|
}
|
|
end
|
|
|
|
# Repopulate the files array
|
|
studioWildwestFiles = Array.new(studioFilesCopy)
|
|
|
|
# Remove the files from the main file list that were overridden
|
|
filesToRemove.each do |f|
|
|
(studioWildwestFiles.length-1).downto(0) {
|
|
|
|
|i| if ( studioWildwestFiles[i].include?(f) ) then
|
|
|
|
studioWildwestFiles.delete_at( i )
|
|
end
|
|
}
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Write to maxscript file
|
|
#---------------------------------------------------------------------
|
|
|
|
# Check if two scripts exist with same name but different parents,
|
|
# as MAX ignores one even in this situation
|
|
menus.each do |menu|
|
|
menus.each do |m|
|
|
if ( m.name == menu.name and m.parent != menu.parent ) then
|
|
errMsg = "There are two menus with the same name:#{m.name } in the #{studioName} folder but different parents (#{m.parent} and #{menu.parent}) - MAX will ignore one of these. Change the folder name in perforce and regenerate again to fix this"
|
|
g_Log.error(errMsg)
|
|
g_Errors << errMsg
|
|
end
|
|
end
|
|
|
|
# Write out the menu and the scripts to place on it
|
|
wildMs.write("\nRsSetMenu \"#{ menu.nameFormatted }\" #(\n")
|
|
firstEntry = true
|
|
menu.items.sort!
|
|
menu.items.each do |mi|
|
|
if ( not firstEntry ) then wildMs.write(",") else firstEntry = false end
|
|
wildMs.write("\n\t\t\"#{ mi }\"")
|
|
end
|
|
wildMs.write(") menuParentName:\"#{ menu.parentFormatted }\"\n")
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Write to macroscript file
|
|
#---------------------------------------------------------------------
|
|
wildMcr.write("\n-- //////////////////////////////////////////\n")
|
|
wildMcr.write("-- #{studioName}\n")
|
|
wildMcr.write("-- //////////////////////////////////////////\n")
|
|
g_Log.debug("#{studioName} scripts to be used:")
|
|
studioWildwestFiles.each do |f|
|
|
g_Log.debug(f)
|
|
wildMcr.write("\nmacroscript #{OS::Path::get_basename(f)}\n")
|
|
wildMcr.write("\tcategory:\"#{formatName(OS::Path::get_trailing_directory(f))}\"\n" )
|
|
wildMcr.write("\tButtonText:\"#{formatName(OS::Path::get_basename(f))}\"\n" )
|
|
wildMcr.write("(\n")
|
|
wildMcr.write("\tfilein (::RsConfigGetWildwestDir() + \"script/max/#{f.sub(OS::Path::normalise(wildDir), '') }\")\n")
|
|
wildMcr.write(")\n")
|
|
end
|
|
|
|
if ( macroscriptOverrides.length > 0 ) then
|
|
g_Log.info("Files being overridden from config file:")
|
|
end
|
|
macroscriptOverrides.each do |o|
|
|
|
|
g_Log.info( o.file )
|
|
# Category, button text and the body part of the maxscript is automatically
|
|
# filled in with defaults if nothing is given in xml file
|
|
wildMcr.write("\nmacroscript #{ OS::Path::get_basename(o.file) }\n")
|
|
wildMcr.write("\tcategory:\"#{formatName( OS::Path::get_trailing_directory( o.file ) )}\"\n" )
|
|
if ( '' == o.buttonText.to_s ) then
|
|
wildMcr.write("\tButtonText:\"#{ formatName(OS::Path::get_basename(o.file) ) }\"\n" )
|
|
errMsg = "No buttonText attribute given in config file for #{ o.file }, default used"
|
|
g_Log.error(errMsg)
|
|
else
|
|
wildMcr.write("\tButtonText:\"#{ o.buttonText }\"\n" )
|
|
end
|
|
|
|
# The following are optional and missed out if nothing present
|
|
wildMcr.write("\ttooltip:\"#{ o.toolTip }\"\n") unless '' == o.toolTip.to_s
|
|
wildMcr.write("\tIcon:#{ o.icon }\n") unless '' == o.icon.to_s
|
|
wildMcr.write("(\n")
|
|
|
|
if ('' == o.maxscriptBody.to_s) then
|
|
wildMcr.write("\tfilein (::RsConfigGetWildwestDir() + \"script/max/#{o.file.sub( OS::Path::normalise( wildDir ), '') }\")\n")
|
|
errMsg = "No maxscript body attribute given in config file for #{ o.file }, default used"
|
|
g_Log.error(errMsg)
|
|
else
|
|
wildMcr.write("\t#{ o.maxscriptBody }\n")
|
|
end
|
|
wildMcr.write(")\n")
|
|
end
|
|
|
|
# See which files aren't in perforce so they're marked for add
|
|
if (studioWildwestFiles.count > 0) then
|
|
p4.run_add('-c', change_id.to_s, studioWildwestFiles)
|
|
end
|
|
end
|
|
|
|
wildMs.close
|
|
wildMcr.close
|
|
end
|
|
|
|
puts("\nFinished regenerating wildwest menu\n")
|
|
if ( g_Errors.length > 0 ) then
|
|
puts("There were errors:\n\n")
|
|
g_Errors.each do |e|
|
|
puts e + "\n\n"
|
|
end
|
|
end
|
|
|
|
rescue SystemExit => ex
|
|
exit( ex.status )
|
|
rescue Exception => ex
|
|
g_Log.exception( ex, 'Unhandled exception' )
|
|
puts "Unhandled exception: #{ex.message}"
|
|
puts ex.backtrace.join("\n")
|
|
exit -1
|
|
end
|
|
end |