# # File:: %RS_TOOLSLIB%/Util/anim/cutscene/zip_animations.rb # Description:: # # Author:: Mike Wilson # 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