Files
2025-09-29 00:52:08 +02:00

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