435 lines
17 KiB
Ruby
Executable File
435 lines
17 KiB
Ruby
Executable File
#
|
|
# File:: %RS_TOOLSLIB%/util/vfx/data_mk_rmptfx_ipt.rb
|
|
# Description:: Build platform-independent RMPTFX IPT file.
|
|
#
|
|
# Author:: David Muir <david.muir@rockstarnorth.com>
|
|
# Date:: 18 October 2012 (AP3, IronRuby)
|
|
# Date:: 3 June 2008 (original)
|
|
#
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'RSG.Base.dll'
|
|
require 'RSG.Base.Configuration.dll'
|
|
require 'RSG.Base.Windows.dll'
|
|
require 'RSG.Pipeline.Core.dll'
|
|
require 'RSG.Pipeline.Content.dll'
|
|
require 'RSG.Pipeline.Services.dll'
|
|
require 'RSG.SourceControl.Perforce.dll'
|
|
include RSG::Base::Collections
|
|
include RSG::Base::Configuration
|
|
include RSG::Base::Logging
|
|
include RSG::Base::Logging::Universal
|
|
include RSG::Base::OS
|
|
include RSG::Base::Windows
|
|
include RSG::Pipeline::Services::Platform::Texture
|
|
include RSG::SourceControl::Perforce
|
|
|
|
require 'mscorlib'
|
|
require 'System.Core'
|
|
include System::Collections::Generic
|
|
include System::IO
|
|
using_clr_extensions System::Linq
|
|
|
|
require 'pipeline/monkey/object'
|
|
require 'pipeline/monkey/string'
|
|
require 'pipeline/os/options'
|
|
include Pipeline
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Constants
|
|
#-----------------------------------------------------------------------------
|
|
|
|
AUTHOR = 'RSGEDI Tools'
|
|
EMAIL = 'RSGEDI Tools <*tools@rockstarnorth.com>'
|
|
OPTIONS = [
|
|
LongOption::new( 'fxlist', LongOption::ArgType.Required, 'f',
|
|
'path to IPT FX list.' ),
|
|
LongOption::new( 'ipt', LongOption::ArgType.Required, 'i',
|
|
'IPT basename (e.g. core, core_e1).' ),
|
|
LongOption::new( 'output', LongOption::ArgType.Required, 'o',
|
|
'path to output IPT file.' ),
|
|
LongOption::new( 'textures', LongOption::ArgType.None, 't',
|
|
'build texture dictionary only.' ),
|
|
LongOption::new( 'metadata', LongOption::ArgType.Required, 'm',
|
|
'texture metadata location (tcs files).' ),
|
|
LongOption::new( 'template', LongOption::ArgType.Required, 'p',
|
|
'texture template for new tcs files' ),
|
|
LongOption::new( 'regentcs', LongOption::ArgType.None, 'z',
|
|
'force regeneration of texture templates' ),
|
|
LongOption::new( 'reconcile', LongOption::ArgType.None, 'r',
|
|
'enable Perforce reconcile offline work (default: false)' ),
|
|
LongOption::new( 'override', LongOption::ArgType.Required, 'v',
|
|
'override directory' ),
|
|
LongOption::new( 'tag', LongOption::ArgType.Optional, '1',
|
|
'override tag directory, for hi/lo vfx' )
|
|
]
|
|
CL_DESCRIPTION = 'Automatic VFX IPT asset build script:'
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Functions
|
|
#-----------------------------------------------------------------------------
|
|
def read_list_from_file( filename )
|
|
list = []
|
|
File::open( filename, 'r' ) do |fp|
|
|
fp.each do |line|
|
|
list << line.chomp.strip unless ( line.starts_with( '#' ) )
|
|
end
|
|
end
|
|
list
|
|
end
|
|
|
|
def read_list_from_dir( path )
|
|
list = []
|
|
Dir::open( path ) do |dir|
|
|
dir.each do |filename|
|
|
next if ( '.' == filename )
|
|
next if ( '..' == filename )
|
|
list << filename
|
|
end
|
|
end
|
|
list
|
|
end
|
|
|
|
# Create texture TCL data as required.
|
|
def create_tcl( log, texture_filename, template, metadata_path, prepend, iptname, temp_dir )
|
|
|
|
texture_basename = Path::GetFileNameWithoutExtension( texture_filename )
|
|
ipt_basename = Path::GetFileNameWithoutExtension( iptname )
|
|
tcl_tempdir = Path::Combine( temp_dir, ipt_basename )
|
|
|
|
tcsname = Path::ChangeExtension( texture_basename, ".tcs" )
|
|
tcspath = Path::Combine( metadata_path, tcsname )
|
|
tcsdir = Path::GetDirectoryName( tcspath )
|
|
|
|
tclname = Path::ChangeExtension( texture_basename, ".tcl" )
|
|
tclpath = Path::Combine( tcl_tempdir, tclname )
|
|
tcldir = Path::GetDirectoryName( tclpath )
|
|
|
|
Directory::CreateDirectory( tcldir ) unless ( Directory::Exists( tcldir ) )
|
|
|
|
source_textures = [].to_clr( System::String )
|
|
if ( not System::IO::File::Exists( tclpath ) ) then
|
|
log.Message( "Creating TCL: #{tclpath}" )
|
|
SpecificationFactory::CreateLink( tclpath, tcspath )
|
|
end
|
|
|
|
dst = "#{prepend}#{tclname}"
|
|
Pair[System::String, System::String]::new( tclpath, dst )
|
|
end
|
|
|
|
# Clean up empty changelists with a particular description.
|
|
def cleanup_changelists( log, p4, config )
|
|
changes = p4.Run( 'changes', false, ['-L', '-u', config.Username, '-s', 'pending', '-c', p4.Client].to_clr(System::String) )
|
|
changes.each do |change|
|
|
next unless ( change['desc'].include?( CL_DESCRIPTION ) )
|
|
|
|
## log.Warning( "Removing empty changelist #{change['change']}." )
|
|
p4.Run( "change", false, ['-d', change['change'].to_s].to_clr(System::String) )
|
|
end
|
|
end
|
|
|
|
# Print usage information to stdout.
|
|
def usage( options )
|
|
puts "#{__FILE__}"
|
|
puts "Usage:"
|
|
puts options.usage()
|
|
exit( 1 )
|
|
end
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Entry-Point
|
|
#-----------------------------------------------------------------------------
|
|
|
|
if ( __FILE__ == $0 ) then
|
|
|
|
# Initialise log and console output.
|
|
LogFactory.Initialize()
|
|
g_Log = LogFactory.CreateUniversalLog('data_mk_rmptfx_ipt')
|
|
LogFactory.CreateApplicationConsoleLogTarget( )
|
|
|
|
g_Options = OS::Options::new( OPTIONS )
|
|
begin
|
|
if ( g_Options.show_help? )
|
|
usage( g_Options )
|
|
end
|
|
|
|
g_Config = RSG::Base::Configuration::ConfigFactory::CreateConfig( )
|
|
g_BranchName = g_Options.has_option?( 'branch' ) ? g_Options.get( 'branch' ) : g_Config.Project.DefaultBranchName
|
|
g_Branch = g_Config.Project.Branches[g_BranchName] if ( g_Config.Project.Branches.ContainsKey( g_BranchName ) )
|
|
if ( g_Branch.nil? ) then
|
|
g_Log.Error( "Invalid branch '#{g_BranchName}' for project '#{g_Config.Project.UIName}'." )
|
|
exit( 1 )
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Parse Command Line
|
|
#---------------------------------------------------------------------
|
|
g_Output = g_Options.has_option?('output') ? ::File::expand_path( g_Options.get('output') ) : nil
|
|
if ( g_Output.nil? ) then
|
|
puts 'No output ipt file specified.'
|
|
usage( g_Options )
|
|
exit( 5 )
|
|
end
|
|
g_FxList = g_Options.has_option?('fxlist') ? ::File::expand_path( g_Options.get('fxlist') ) : nil
|
|
if ( g_FxList.nil? or ( not ::File::directory?( g_FxList ) ) ) then
|
|
puts 'No or invalid input FX List directory specified.'
|
|
usage( g_Options )
|
|
exit( 6 )
|
|
end
|
|
g_Ipt = g_Options.has_option?('ipt') ? g_Options.get('ipt') : nil
|
|
if ( g_Ipt.nil? ) then
|
|
puts 'No ipt basename specified.'
|
|
usage( g_Options )
|
|
exit( 7 )
|
|
end
|
|
# trying to retrieve the tag for hi/lo redirection and potential override
|
|
g_Tag = g_Options.has_option?('tag') ? g_Options.get('tag') : nil
|
|
if ( g_Tag.nil? ) then
|
|
puts 'No tag specified, falling back to default'
|
|
else
|
|
puts "Tag specified : #{g_Tag}"
|
|
end
|
|
|
|
g_OverrideDir = g_Options.get('override')
|
|
g_TexturesOnly = g_Options.get('textures')
|
|
g_regentcs = g_Options.get('regentcs')
|
|
g_metadata_path = g_Options.get('metadata')
|
|
g_metadata_template = g_Options.get('template')
|
|
g_P4Reconcile = g_Options.has_option?( 'reconcile' ) ? g_Options.get('reconcile') : false
|
|
|
|
#---------------------------------------------------------------------
|
|
# Process our lists for dependencies
|
|
#---------------------------------------------------------------------
|
|
root = Path::GetDirectoryName( g_FxList )
|
|
list_effects_filename = Path::Combine( g_FxList, g_Ipt, "#{g_Ipt}.effectlist" ) # ME
|
|
list_emit_filename = Path::Combine( g_FxList, g_Ipt, "#{g_Ipt}.emitlist" ) # ME
|
|
list_model_filename = Path::Combine( g_FxList, g_Ipt, "#{g_Ipt}.modellist" )
|
|
list_ptx_filename = Path::Combine( g_FxList, g_Ipt, "#{g_Ipt}.ptxlist" ) # ME
|
|
list_texture_filename = Path::Combine( g_FxList, g_Ipt, "#{g_Ipt}.texlist" )
|
|
|
|
list_fxlists = read_list_from_dir( Path::Combine( g_FxList, g_Ipt ) )
|
|
list_effectrules = read_list_from_file( list_effects_filename )
|
|
list_emitrules = read_list_from_file( list_emit_filename )
|
|
list_models = read_list_from_file( list_model_filename )
|
|
list_ptxrules = read_list_from_file( list_ptx_filename )
|
|
list_textures = read_list_from_file( list_texture_filename )
|
|
|
|
Directory::CreateDirectory( Path::GetDirectoryName( g_Output ) ) if ( !Directory::Exists( Path::GetDirectoryName( g_Output ) ) )
|
|
iptname = Path::GetFileNameWithoutExtension( g_Output )
|
|
temp_dir = Path::Combine( g_Config.ToolsTemp, "metadata" )
|
|
Directory::Delete( temp_dir, true ) if ( Directory::Exists( temp_dir ) )
|
|
|
|
Directory::SetCurrentDirectory( g_Config.Project.Root )
|
|
g_P4 = g_Config.Project.SCM if ( g_P4Reconcile )
|
|
g_Changelist = g_P4.CreatePendingChangelist( "#{CL_DESCRIPTION} #{iptname}" ) if ( g_P4Reconcile )
|
|
g_ChangelistNumber = g_Changelist.Number if ( g_P4Reconcile )
|
|
|
|
g_DepLists = System::Collections::Generic::List[Pair[System::String, System::String]]::new( )
|
|
g_FileList = System::Collections::Generic::List[Pair[System::String, System::String]]::new( )
|
|
|
|
if ( g_TexturesOnly ) then
|
|
#-----------------------------------------------------------------
|
|
# Build Texture Dictionary Only
|
|
#-----------------------------------------------------------------
|
|
list_models.each do |model|
|
|
modelname = model.split( '/' )[0]
|
|
modeldir = OS::Path::combine( root, 'models', modelname )
|
|
Dir::open( modeldir ) do |dir|
|
|
dir.each do |filename|
|
|
next if ( '.' == filename )
|
|
next if ( '..' == filename )
|
|
fullpath = OS::Path::combine( modeldir, filename )
|
|
if ( '.dds' == Path::GetExtension( fullpath ) ) then
|
|
# Add modeltex file
|
|
entry = {}
|
|
entry[:src] = fullpath
|
|
entry[:dst] = filename
|
|
g_FileList << entry
|
|
end
|
|
end
|
|
end
|
|
end
|
|
list_textures.each do |texture|
|
|
fullname = "#{texture}.dds"
|
|
fullpath = Path::Combine( root, 'textures', fullname )
|
|
|
|
entry = Pair::new( fullpath, fullname )
|
|
g_FileList << entry
|
|
|
|
g_FileList << create_tcl( g_Log, fullpath, g_metadata_template, g_metadata_path, "", iptname, temp_dir )
|
|
end
|
|
|
|
else
|
|
#-----------------------------------------------------------------
|
|
# Build Full IPT RPF
|
|
#-----------------------------------------------------------------
|
|
g_Log.Message( "Adding #{list_fxlists.size} fxlists..." )
|
|
list_fxlists.each do |fxlist|
|
|
src = Path::Combine( root, 'fxlists', g_Ipt, fxlist ).to_clr_string
|
|
dst = "fxlists/#{g_Ipt}/#{fxlist}".to_clr_string
|
|
entry = Pair[System::String, System::String]::new( src, dst )
|
|
g_DepLists << entry
|
|
end
|
|
g_Log.Message( "Adding #{list_effectrules.size} effect rules..." )
|
|
list_effectrules.each do |effectrule|
|
|
fullname = "#{effectrule}.effectrule"
|
|
|
|
if g_Tag.nil?
|
|
src = Path::Combine( root, 'effectrules', fullname ).to_clr_string
|
|
else
|
|
src = Path::Combine( root, 'effectrules', g_Tag, fullname ).to_clr_string
|
|
unless File.exist?( src )
|
|
src = Path::Combine( root, 'effectrules', fullname ).to_clr_string
|
|
else
|
|
print "effectrules #{src} \n"
|
|
end
|
|
end
|
|
|
|
dst = "effectrules/#{fullname}"
|
|
srcoverride = Path::Combine( root, 'effectrules', g_OverrideDir, fullname ) if g_OverrideDir != nil
|
|
if srcoverride != nil and File.exist?(srcoverride) then
|
|
g_Log.Message( "Effect rule override has been found for #{g_OverrideDir} at #{srcoverride}" )
|
|
g_FileList << Pair[System::String, System::String]::new( srcoverride, dst )
|
|
else
|
|
g_FileList << Pair[System::String, System::String]::new( src, dst )
|
|
end
|
|
end
|
|
g_Log.Message( "Adding #{list_emitrules.size} emit rules..." )
|
|
list_emitrules.each do |emitrule|
|
|
fullname = "#{emitrule}.emitrule"
|
|
|
|
if g_Tag.nil?
|
|
src = Path::Combine( root, 'emitrules', fullname ).to_clr_string
|
|
else
|
|
src = Path::Combine( root, 'emitrules', g_Tag, fullname ).to_clr_string
|
|
unless File.exist?( src )
|
|
src = Path::Combine( root, 'emitrules', fullname ).to_clr_string
|
|
end
|
|
end
|
|
|
|
dst = "emitrules/#{fullname}"
|
|
srcoverride = Path::Combine( root, 'emitrules', g_OverrideDir, fullname ) if g_OverrideDir != nil
|
|
if srcoverride != nil and File.exist?(srcoverride) then
|
|
g_Log.Message( "Emit rule override has been found for #{g_OverrideDir} at #{srcoverride}" )
|
|
g_FileList << Pair[System::String, System::String]::new( srcoverride, dst )
|
|
else
|
|
g_FileList << Pair[System::String, System::String]::new( src, dst )
|
|
end
|
|
end
|
|
g_Log.Message( "Adding #{list_models.size} models..." )
|
|
list_models.each do |model|
|
|
modelname = model.split('/')[0]
|
|
modeldir = Path::Combine( root, 'models', modelname )
|
|
Dir::open( modeldir ) do |dir|
|
|
dir.each do |filename|
|
|
next if ( '.' == filename )
|
|
next if ( '..' == filename )
|
|
fullpath = Path::Combine( modeldir, filename )
|
|
if ( '.dds' == Path::GetExtension( fullpath ) ) then
|
|
# Add modeltex file
|
|
src = fullpath
|
|
dst = "modeltex/#{filename}"
|
|
entry = Pair[System::String, System::String]::new( src, dst )
|
|
g_FileList << entry
|
|
else
|
|
# Add model file
|
|
src = fullpath
|
|
dst = "models/#{modelname}/#{filename}"
|
|
entry = Pair[System::String, System::String]::new( src, dst )
|
|
g_FileList << entry
|
|
end
|
|
end
|
|
end
|
|
end
|
|
g_Log.Message( "Adding #{list_ptxrules.size} PTX rules..." )
|
|
list_ptxrules.each do |ptxrule|
|
|
fullname = "#{ptxrule}.ptxrule"
|
|
|
|
if g_Tag.nil?
|
|
src = Path::Combine( root, 'ptxrules', fullname ).to_clr_string
|
|
else
|
|
src = Path::Combine( root, 'ptxrules', g_Tag, fullname )
|
|
unless File.exist?( src )
|
|
src = Path::Combine( root, 'ptxrules', fullname )
|
|
end
|
|
end
|
|
|
|
dst = "ptxrules/#{fullname}"
|
|
srcoverride = Path::Combine( root, 'ptxrules', g_OverrideDir, fullname ) if g_OverrideDir != nil
|
|
if srcoverride != nil and File.exist?(srcoverride) then
|
|
g_Log.Message( "PTX rule override has been found for #{g_OverrideDir} at #{srcoverride}" )
|
|
g_FileList << Pair[System::String, System::String]::new( srcoverride, dst )
|
|
else
|
|
g_FileList << Pair[System::String, System::String]::new( src, dst )
|
|
end
|
|
end
|
|
g_Log.Message( "Adding #{list_textures.size} textures..." )
|
|
list_textures.each do |texture|
|
|
fullname = "#{texture}.dds"
|
|
fullpath = Path::Combine( root, 'textures', fullname )
|
|
|
|
if ( File::exists?( fullpath ) ) then
|
|
src = fullpath
|
|
dst = "textures/#{fullname}"
|
|
entry = Pair[System::String, System::String]::new( src, dst )
|
|
g_FileList << entry
|
|
|
|
g_FileList << create_tcl( g_Log, fullpath, g_metadata_template, g_metadata_path, "textures/", iptname, temp_dir )
|
|
end
|
|
end
|
|
end
|
|
# Perforce check that everything is in the depot.
|
|
if ( g_P4Reconcile and g_FileList.size > 0 ) then
|
|
g_Log.Message( "Reconciling offline work with Perforce depot..." )
|
|
files = g_FileList.to_seq
|
|
files = files.Where( lambda { |p| ".tcl" != Path::GetExtension(p.First) } ).
|
|
Select( lambda { |p| Path::GetFullPath( p.First.ToLower() ) } ).to_a.to_clr( System::String )
|
|
g_Log.Message( "#{files.Count()} source files..." )
|
|
|
|
fileStates = FileState::Create( g_P4, files )
|
|
filesInP4 = fileStates.Select( lambda { |f| Path::GetFullPath( f.ClientFilename.ToLower() ) } )
|
|
filesNotInP4 = files.Except( filesInP4 )
|
|
g_Log.Message( "#{files.Count()} files, #{filesInP4.Count()} already in Perforce, #{filesNotInP4.Count()} needing added." )
|
|
|
|
filesNotInP4.each do |f|
|
|
g_P4.Run( "add", false, ['-c', g_ChangelistNumber.to_s, f].to_clr(System::String) )
|
|
end
|
|
end
|
|
# Perforce revert unchanged; delete changelist if empty.
|
|
g_P4.Run( "revert", false, ['-a', '-c', g_ChangelistNumber.to_s].to_clr(System::String) ) if ( g_P4Reconcile )
|
|
cleanup_changelists( g_Log, g_P4, g_Config ) if ( g_P4Reconcile )
|
|
|
|
if ( System::IO::File::Exists( g_Output ) ) then
|
|
g_Log.Message( "Deleting existing output: #{g_Output}" )
|
|
System::IO::File::Delete( g_Output )
|
|
end
|
|
|
|
filelist = g_FileList.AddRange( g_DepLists ).to_a
|
|
g_Log.Message( "Creating: #{g_Output}..." )
|
|
Zip::Create( g_Branch, g_Output, (g_FileList + []).to_clr( Pair[System::String,System::String] ) )
|
|
g_Log.Message( "Done." )
|
|
|
|
rescue SystemExit => ex
|
|
exit( ex.status )
|
|
|
|
rescue Exception => ex
|
|
|
|
g_Log.Exception( ex, "Exception during #{__FILE__}." )
|
|
|
|
puts "Exception during #{__FILE__}: #{ex.message}"
|
|
puts ex.backtrace.join("\n")
|
|
|
|
unless ( g_Options.is_enabled?( 'nopopups' ) ) then
|
|
dlg = RSG::Base::Windows::ExceptionStackTraceDlg::new( ex,
|
|
g_Config.EmailAddress, AUTHOR, EMAIL )
|
|
dlg.ShowDialog( )
|
|
end
|
|
|
|
exit( 1 )
|
|
end
|
|
end
|
|
|
|
# data_mk_rmptfx_ipt.rb
|