408 lines
16 KiB
Ruby
Executable File
408 lines
16 KiB
Ruby
Executable File
#
|
|
# File:: %RS_TOOLSLIB%/Util/anim/cutscene/zip_animations.rb
|
|
# Description::
|
|
#
|
|
# Author:: Mike Wilson <mike.wilson@rockstarnorth.com>
|
|
# Date:: 15 January 2013
|
|
#
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'RSG.Base.dll'
|
|
require 'RSG.Base.Configuration.dll'
|
|
require 'RSG.Base.Windows.dll'
|
|
require 'RSG.SourceControl.Perforce'
|
|
include RSG::Base::Configuration
|
|
include RSG::Base::Logging
|
|
include RSG::Base::Logging::Universal
|
|
include RSG::Base::OS
|
|
include RSG::Base::Windows
|
|
include RSG::SourceControl::Perforce
|
|
|
|
require 'mscorlib'
|
|
require 'System.Core'
|
|
require 'System.Xml'
|
|
include System
|
|
include System::Collections::Generic
|
|
include System::IO
|
|
include System::Diagnostics
|
|
include System::Xml
|
|
|
|
require 'pipeline/os/options'
|
|
require 'pipeline/monkey/array'
|
|
|
|
require 'RSG.ManagedRage.dll'
|
|
include RSG::ManagedRage::ClipAnimation
|
|
|
|
include Pipeline
|
|
#-----------------------------------------------------------------------------
|
|
# Constants
|
|
#-----------------------------------------------------------------------------
|
|
OPTIONS = [
|
|
LongOption::new( 'rebuild', LongOption::ArgType.None, 'f', 'Rebuild data; ignoring timestamp information.' ),
|
|
LongOption::new( 'checkout', LongOption::ArgType.None, 'c', 'Checkout data from p4.' ),
|
|
LongOption::new( 'nosync', LongOption::ArgType.Optional, 's', 'Prevents automatic syncing.' ),
|
|
LongOption::new( 'manifest', LongOption::ArgType.Optional, 'm', 'Manifest file override.' ),
|
|
LongOption::new( 'synccl', LongOption::ArgType.Optional, 'e', 'Sync specific changelists.' ),
|
|
LongOption::new( 'supportcore', LongOption::ArgType.None, 'c', 'Handle cutscenes in core' ),
|
|
LongOption::new( 'supportdlc', LongOption::ArgType.None, 'c', 'Handle cutscenes in dlcs' ),
|
|
]
|
|
|
|
def get_latest_time_from_dir(src_dir)
|
|
|
|
time = DateTime::new(2011,1,1)
|
|
files = Directory.GetFiles(src_dir, "*.*", SearchOption.AllDirectories)
|
|
|
|
files.each do | file |
|
|
if(System::IO::File.GetLastWriteTime(file).CompareTo(time) > 0) then
|
|
time = System::IO::File.GetLastWriteTime(file)
|
|
end
|
|
end
|
|
|
|
return time
|
|
end
|
|
|
|
def print_return( ret, log)
|
|
ret.Messages.each do | msg |
|
|
log.Message(msg)
|
|
end
|
|
|
|
ret.Errors.each do | msg |
|
|
log.Error(msg)
|
|
end
|
|
end
|
|
|
|
def checkout_files( p4, changelist, file_mapping, log)
|
|
|
|
main_args = ['-c', changelist.Number.to_s]
|
|
second_args = ['-t', '+l']
|
|
file_mapping.each do | zip_dictionary |
|
|
if p4.Exists(zip_dictionary.DepotFilename)
|
|
log.Message("Marking for edit #{zip_dictionary.DepotFilename}")
|
|
args = main_args + [zip_dictionary.DepotFilename]
|
|
args = System::Array[System::String].new(args)
|
|
|
|
ret = p4.Run('edit', args)
|
|
else
|
|
log.Message("Marking for add #{zip_dictionary.DepotFilename}")
|
|
args = main_args + ['-f', '-tbinary+Flw', zip_dictionary.LocalFilename]
|
|
args = System::Array[System::String].new(args)
|
|
|
|
ret = p4.Run('add', args)
|
|
end
|
|
|
|
print_return(ret, log)
|
|
log.Message("about to re-open #{zip_dictionary}")
|
|
args = second_args + [zip_dictionary.DepotFilename]
|
|
args = System::Array[System::String].new(args)
|
|
ret = p4.Run('reopen', args)
|
|
print_return(ret, log)
|
|
end
|
|
|
|
end
|
|
|
|
def generate_master_icd_list(config, rebuild, log, master_list_filename, src_dir, dest_dir, export_dir )
|
|
result = true
|
|
System::IO::File.Delete(master_list_filename) if System::IO::File.Exists(master_list_filename)
|
|
directory_list = Directory.GetDirectories(src_dir)
|
|
|
|
MClip.Init()
|
|
|
|
xml_writer_settings = XmlWriterSettings.new()
|
|
xml_writer_settings.Indent = true
|
|
xml_writer_settings.IndentChars = "\t"
|
|
|
|
xml_writer = XmlWriter.Create(master_list_filename, xml_writer_settings)
|
|
xml_writer.WriteStartDocument()
|
|
xml_writer.WriteStartElement("Assets")
|
|
|
|
dest_dir = dest_dir.Replace('\\', '/')
|
|
#sceneSplit = dest_dir.Split("/")
|
|
|
|
directory_list.each do | dir |
|
|
|
|
|
|
clip_list = Directory.GetFiles(dir, ("*.clip"));
|
|
|
|
# Skip empty folders
|
|
if(clip_list.Length == 0) then
|
|
next
|
|
end
|
|
|
|
found_property = false
|
|
clip_list.each do | clip |
|
|
rageClip = MClip::new(0) # this is MClip.ClipType.Normal
|
|
if(rageClip.Load(clip) == true) then
|
|
fbx_property = rageClip.FindProperty("FBXFile_DO_NOT_RESOURCE")
|
|
if (fbx_property != nil) then
|
|
found_property = true
|
|
fbx_file = (fbx_property.GetPropertyAttributes()[0]).GetString()
|
|
fbx_file = config.Project.DefaultBranch.Environment.Subst(fbx_file)
|
|
fbx_file = fbx_file.Replace('\\','/')
|
|
fbx_file = fbx_file.ToLower();
|
|
|
|
sceneSplit = dest_dir.Split("/")
|
|
|
|
# HACK FOR FBX FILES WITH ASSETS PATH FOR FBX
|
|
if fbx_file.StartsWith("x:/gta5/art/animation/cutscene/!!scenes", StringComparison.OrdinalIgnoreCase) == true then
|
|
tempDestDir = System::String.Format("x:/gta5/art/animation/cutscene/!!scenes")
|
|
sceneSplit = tempDestDir.Split("/")
|
|
end
|
|
|
|
if fbx_file.StartsWith("x:/gta5/art/ng/animation/cutscene/!!scenes", StringComparison.OrdinalIgnoreCase) == true then
|
|
tempDestDir = System::String.Format("x:/gta5/art/ng/animation/cutscene/!!scenes")
|
|
sceneSplit = tempDestDir.Split("/")
|
|
end
|
|
|
|
assetSplit = fbx_file.Split('/');
|
|
|
|
missionName = ""
|
|
|
|
begin
|
|
missionName = assetSplit[sceneSplit.Length]
|
|
|
|
# We have some clips that have x:\depot\gta5, this messes up the mapping
|
|
if missionName == "!!scenes" then
|
|
log.Warning("Clip contains mission !!scenes - " + fbx_file + " - " + clip)
|
|
break;
|
|
end
|
|
|
|
rescue Exception => ex
|
|
log.Error("Unable to generate zip for '" + Path.GetFileName(dir) + "' - " + ex.message + " - " + fbx_file + " | " + dest_dir + " | " + clip)
|
|
break
|
|
end
|
|
|
|
zipFileName = System::String.Format("{0}/{1}/{2}.icd.zip", export_dir, missionName, Path.GetFileName(dir))
|
|
zipFileName = config.Project.DefaultBranch.Environment.Subst(zipFileName)
|
|
|
|
# check if the data is newer than the zip
|
|
if(rebuild == false) then
|
|
if(System::IO::File.Exists(zipFileName)) then
|
|
zipTime = System::IO::File.GetLastWriteTime(zipFileName)
|
|
dirtime = System::IO::Directory.GetLastWriteTime(dir)
|
|
fileTime = get_latest_time_from_dir(dir)
|
|
|
|
if (zipTime.CompareTo(fileTime) > 0 && zipTime.CompareTo(dirtime) > 0) then
|
|
break;
|
|
end
|
|
end
|
|
end
|
|
|
|
xml_writer.WriteStartElement("ZipArchive")
|
|
xml_writer.WriteAttributeString("path", zipFileName)
|
|
xml_writer.WriteStartElement("Directory")
|
|
xml_writer.WriteAttributeString("path", dir)
|
|
xml_writer.WriteEndElement()
|
|
|
|
firstlvl_sub = Directory.GetDirectories(dir)
|
|
firstlvl_sub.each do | first_sub |
|
|
if(Path.GetFileName(first_sub).ToLower() != "obj" && Path.GetFileName(first_sub).ToLower() != "toybox") then
|
|
xml_writer.WriteStartElement("Directory")
|
|
xml_writer.WriteAttributeString("path", dir + "\\" + Path.GetFileName(first_sub))
|
|
xml_writer.WriteAttributeString("destination", "\\" + Path.GetFileName(first_sub))
|
|
xml_writer.WriteEndElement();
|
|
end
|
|
|
|
secondlvl_sub = Directory.GetDirectories(first_sub)
|
|
secondlvl_sub.each do | second_sub |
|
|
if(Path.GetFileName(second_sub).ToLower() != "toybox") then
|
|
xml_writer.WriteStartElement("Directory")
|
|
xml_writer.WriteAttributeString("path", first_sub + "\\" + Path.GetFileName(second_sub))
|
|
xml_writer.WriteAttributeString("destination", Path.GetFileName(first_sub) + "\\" + Path.GetFileName(second_sub))
|
|
|
|
xml_writer.WriteEndElement();
|
|
end
|
|
end
|
|
end
|
|
xml_writer.WriteEndElement()
|
|
break
|
|
end
|
|
end
|
|
rageClip.Dispose()
|
|
end
|
|
|
|
if(found_property != true) then
|
|
log.Warning("No clips contain property FBXFile_DO_NOT_RESOURCE - '" + Path.GetFileName(dir) + "'")
|
|
end
|
|
end
|
|
xml_writer.WriteEndDocument()
|
|
xml_writer.Close()
|
|
|
|
return result
|
|
end
|
|
|
|
def run( config, rebuild, checkout, log, no_sync, no_popups, support_core, support_dlc )
|
|
|
|
dlc_project_name = []
|
|
dlc_project_art_dirs = []
|
|
dlc_project_export_dirs = []
|
|
dlc_project_asset_dirs = []
|
|
dlc_project_master_files = []
|
|
|
|
projectDictionary = nil
|
|
|
|
if support_dlc and not support_core
|
|
projectDictionary = config.DLCProjects
|
|
elsif not support_dlc and support_core
|
|
projectDictionary = System::Collections::Generic::Dictionary[System::String, RSG::Base::Configuration::IProject].new
|
|
projectDictionary.Add(config.Project.Name, config.Project)
|
|
else
|
|
projectDictionary = config.AllProjects
|
|
end
|
|
|
|
projectDictionary.each do | pair |
|
|
dlc_project_name << pair.Value.DefaultBranch.Project.Name
|
|
dlc_project_art_dirs << pair.Value.DefaultBranch.Art + "/animation/cutscene/!!scenes"
|
|
dlc_project_export_dirs << pair.Value.DefaultBranch.Export + "/anim/cutscene/"
|
|
dlc_project_asset_dirs << pair.Value.DefaultBranch.Assets + "/cuts"
|
|
dlc_project_master_files << pair.Value.DefaultBranch.Export + "/anim/cutscene/master_icd_list.xml"
|
|
end
|
|
|
|
log.Message("Found '#{projectDictionary.count}' DLC Projects")
|
|
|
|
changelist = nil
|
|
p4 = config.Project.SCMConnect()
|
|
|
|
if checkout then
|
|
changelistname = 'Cutscene DLC Animation Dictionaries [Asset Pipeline 3.0]'
|
|
changelist = p4.CreatePendingChangelist(changelistname)
|
|
end
|
|
|
|
(0..dlc_project_art_dirs.length-1).each do |num|
|
|
|
|
src_dir = dlc_project_art_dirs[num]
|
|
dest_dir = dlc_project_export_dirs[num]
|
|
master_list_filename = dlc_project_master_files[num]
|
|
asset_dir = dlc_project_asset_dirs[num]
|
|
dlc_name = dlc_project_name[num]
|
|
|
|
argsbb = ['-m1', '-ssubmitted', Path.Combine(asset_dir, '...')]
|
|
retbb = p4.Run( 'changes', System::Array[System::String].new(argsbb))
|
|
if(retbb.Records.Length < 1) then
|
|
log.Message("Path: '#{asset_dir}' does not exist in perforce. Skipping.");
|
|
next
|
|
end
|
|
|
|
begin
|
|
if not no_sync then
|
|
log.Message("Syncing: #{dest_dir}")
|
|
ret = p4.Run( 'sync', true, Path.Combine( dest_dir, '...' ) )
|
|
print_return(ret, log)
|
|
log.Message("Sync complete")
|
|
|
|
log.Message("Syncing: #{asset_dir}")
|
|
ret = p4.Run( 'sync', Path.Combine( asset_dir, '...' ) )
|
|
print_return(ret, log)
|
|
log.Message("Sync complete")
|
|
end
|
|
|
|
if (Directory.Exists( dest_dir) == false) then
|
|
Directory.CreateDirectory( dest_dir )
|
|
end
|
|
|
|
if (not Directory.Exists(asset_dir)) then
|
|
log.Message("Path: '#{asset_dir}' does not exist locally, This project cannot be packaged without this directory being synced. Skipping.");
|
|
next
|
|
end
|
|
|
|
zip_files = []
|
|
if checkout then
|
|
log.Message("Checking out dictionaries...")
|
|
zip_files = Directory.GetFiles( dest_dir, "*.icd.zip", SearchOption.AllDirectories )
|
|
if zip_files.length > 0
|
|
file_mapping = FileMapping.Create(p4, System::Array[System::String].new(zip_files))
|
|
|
|
if file_mapping.count > 0 then
|
|
checkout_files(p4, changelist, file_mapping, log)
|
|
end
|
|
end
|
|
log.Message("Check out complete.")
|
|
end
|
|
|
|
if(generate_master_icd_list(config, rebuild, log, master_list_filename, asset_dir, src_dir, dest_dir)) then
|
|
log.Message("Zipping clip dictionaries...")
|
|
ir_exe = config.ToolsRoot + "\\ironlib\\lib\\RSG.Pipeline.Convert.exe"
|
|
args = ""
|
|
args = args + " --rebuild" if rebuild == true
|
|
args = args + " --branch " + config.Project.DefaultBranchName
|
|
if projectDictionary.ContainsKey(dlc_name) and projectDictionary[dlc_name].IsDLC then
|
|
args = args + " --dlc " + dlc_name
|
|
end
|
|
args = args + " #{master_list_filename}"
|
|
|
|
ir_exe = config.Environment.Subst(ir_exe)
|
|
args = config.Environment.Subst(args)
|
|
log.Message("Asset pack command #{ir_exe} #{args}")
|
|
startInfo = ProcessStartInfo::new()
|
|
startInfo.Arguments = args
|
|
startInfo.FileName = ir_exe
|
|
startInfo.UseShellExecute = false
|
|
|
|
proc = System::Diagnostics::Process.Start(startInfo)
|
|
proc.WaitForExit()
|
|
exit_code = proc.ExitCode
|
|
log.Error("Errors zipping animations, see log") if (exit_code != 0)
|
|
else
|
|
log.Error("Errors generating master icd list") if (exit_code != 0)
|
|
end
|
|
|
|
if checkout then
|
|
post_zip_files = Directory.GetFiles( dest_dir, "*.icd.zip", SearchOption.AllDirectories )
|
|
new_zip_files = Array::from_clr(post_zip_files).reject { |zip_file| zip_files.include?(zip_file) }
|
|
|
|
if(new_zip_files.count > 0) then
|
|
file_mapping = FileMapping.Create(p4, System::Array[System::String].new(new_zip_files))
|
|
if file_mapping.count > 0 then
|
|
log.Message("Adding new zip files")
|
|
log.Message("New files: #{new_zip_files.inspect}")
|
|
checkout_files(p4, changelist, file_mapping, log)
|
|
|
|
end
|
|
end
|
|
log.Message("Reverting unchanged dictionaries...")
|
|
args = ['-a', Path.Combine( dest_dir, '...' )]
|
|
p4.Run('revert', System::Array[System::String].new(args))
|
|
end
|
|
ensure
|
|
p4.Disconnect()
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Implementation
|
|
#-----------------------------------------------------------------------------
|
|
if ( __FILE__ == $0 ) then
|
|
#-------------------------------------------------------------------------
|
|
# Entry-Point
|
|
#-------------------------------------------------------------------------
|
|
LogFactory.Initialize( )
|
|
LogFactory.CreateApplicationConsoleLogTarget( )
|
|
g_Log = LogFactory.ApplicationLog
|
|
begin
|
|
|
|
g_Options = OS::Options::new( OPTIONS )
|
|
|
|
g_Config = RSG::Base::Configuration::ConfigFactory::CreateConfig( )
|
|
g_Rebuild = ( g_Options.is_enabled?( 'rebuild' ) )
|
|
g_Checkout = ( g_Options.is_enabled?( 'checkout' ) )
|
|
g_NoSync = (g_Options.has_option?( 'nosync' ))
|
|
g_NoPopups = ( g_Options.is_enabled?( 'nopopups' ) )
|
|
g_SupportCore = ( g_Options.is_enabled?( 'supportcore' ) )
|
|
g_SupportDLC = ( g_Options.is_enabled?( 'supportdlc' ) )
|
|
|
|
# Initialise log and console output.
|
|
run( g_Config, g_Rebuild, g_Checkout, g_Log, g_NoSync, g_NoPopups, g_SupportCore, g_SupportDLC )
|
|
|
|
LogFactory.ApplicationShutdown()
|
|
LogFactory.ShowUniversalLogViewer(LogFactory.ApplicationLogFilename()) if ((not g_NoPopups) and g_Log.HasErrors())
|
|
|
|
rescue Exception => ex
|
|
g_Log.Exception( ex, "Exception during #{__FILE__}: #{ex.message}")
|
|
|
|
LogFactory.ApplicationShutdown()
|
|
LogFactory.ShowUniversalLogViewer(LogFactory.ApplicationLogFilename()) if ((not g_NoPopups) and g_Log.HasErrors())
|
|
end
|
|
end
|