diff --git a/UnityRiftCLI/Exporter.cs b/UnityRiftCLI/Exporter.cs index 22953c2..94a73f1 100644 --- a/UnityRiftCLI/Exporter.cs +++ b/UnityRiftCLI/Exporter.cs @@ -337,6 +337,7 @@ private static void ExportFbx(IImported convert, string exportPath) ScaleFactor = CLIOptions.o_fbxScaleFactor.Value, ExportAllUvsAsDiffuseMaps = CLIOptions.f_fbxUvsAsDiffuseMaps.Value, ExportAnimations = CLIOptions.o_fbxAnimMode.Value != AnimationExportMode.Skip, + FbxFormat = CLIOptions.f_fbxAsciiFormat.Value ? 1 : 0, }; ModelExporter.ExportFbx(exportPath, convert, fbxSettings); } diff --git a/UnityRiftCLI/Options/CLIOptions.cs b/UnityRiftCLI/Options/CLIOptions.cs index a81bb78..138c9b6 100644 --- a/UnityRiftCLI/Options/CLIOptions.cs +++ b/UnityRiftCLI/Options/CLIOptions.cs @@ -118,6 +118,7 @@ internal static class CLIOptions public static bool convertTexture; public static Option o_imageFormat; public static Option o_audioFormat; + public static Option f_spriteExportWithCanvas; //live2d public static Option o_l2dGroupOption; public static Option f_l2dAssetSearchByFilename; @@ -130,18 +131,21 @@ internal static class CLIOptions public static Option o_fbxBoneSize; public static Option o_fbxAnimMode; public static Option f_fbxUvsAsDiffuseMaps; + public static Option f_fbxAsciiFormat; //filter public static Option> o_filterByName; public static Option> o_filterByContainer; public static Option> o_filterByPathID; public static Option> o_filterByText; public static Option f_filterWithRegex; + public static Option f_filterExcludeMode; //advanced public static Option o_bundleBlockInfoCompression; public static Option o_bundleBlockCompression; public static Option o_maxParallelExportTasks; public static Option o_exportAssetList; public static Option o_assemblyPath; + public static Option o_stripPathPrefix; public static Option o_typeTreeDBPath; public static Option o_unityVersion; public static Option f_decompressToDisk; @@ -355,6 +359,16 @@ private static void InitOptions() optionExample: "Example: \"--audio-format wav\"", optionHelpGroup: HelpGroups.Convert ); + f_spriteExportWithCanvas = new GroupedOption + ( + optionDefaultValue: false, + optionName: "--sprite-canvas", + optionDescription: "(Flag) If specified, sprites are exported at their full authored\n" + + "size (m_Rect) with transparent padding, instead of just the cropped region", + optionExample: "", + optionHelpGroup: HelpGroups.Convert, + isFlag: true + ); #endregion #region Init Cubism Live2D Options @@ -463,6 +477,15 @@ private static void InitOptions() optionExample: "", optionHelpGroup: HelpGroups.FBX ); + f_fbxAsciiFormat = new GroupedOption + ( + optionDefaultValue: false, + optionName: "--fbx-ascii-format", + optionDescription: "(Flag) If specified, Studio will export FBX in ASCII format.\n" + + "If not specified, Binary format will be used", + optionExample: "", + optionHelpGroup: HelpGroups.FBX + ); #endregion #region Init Filter Options @@ -513,6 +536,16 @@ private static void InitOptions() optionHelpGroup: HelpGroups.Filter, isFlag: true ); + f_filterExcludeMode = new GroupedOption + ( + optionDefaultValue: false, + optionName: "--filter-exclude-mode", + optionDescription: "(Flag) If specified, the filter options will work as an exclusion\n" + + "(i.e. assets that match the filter conditions will be excluded)", + optionExample: "", + optionHelpGroup: HelpGroups.Filter, + isFlag: true + ); #endregion #region Init Advanced Options @@ -678,6 +711,15 @@ private static void InitOptions() optionExample: "", optionHelpGroup: HelpGroups.Advanced ); + o_stripPathPrefix = new GroupedOption + ( + optionDefaultValue: "", + optionName: "--strip-path-prefix ", + optionDescription: "Specify a path prefix to be stripped from exported asset container paths\n" + + "(applies to the ContainerPath / ContainerPathFull group options)\n", + optionExample: "Example: \"--strip-path-prefix assets/models/char/\"\n", + optionHelpGroup: HelpGroups.Advanced + ); o_typeTreeDBPath = new GroupedOption ( optionDefaultValue: "", @@ -889,6 +931,7 @@ public static void ParseArgs(string[] args) o_exportAssetTypes.Value = new List { ClassIDType.Animator, + ClassIDType.AnimationClip, // needed so --fbx-animation all can bind clips ClassIDType.Mesh, ClassIDType.Texture2D, }; @@ -948,7 +991,7 @@ public static void ParseArgs(string[] args) flagIndexes.Add(i); break; case "--fbx-uvs-as-diffuse": - if (o_workMode.Value != WorkMode.SplitObjects) + if (o_workMode.Value != WorkMode.SplitObjects && o_workMode.Value != WorkMode.Animator) { Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{flag.Color(brightYellow)}] flag. This flag is not suitable for the current working mode [{o_workMode.Value}].\n"); ShowOptionDescription(o_workMode); @@ -957,10 +1000,22 @@ public static void ParseArgs(string[] args) f_fbxUvsAsDiffuseMaps.Value = true; flagIndexes.Add(i); break; + case "--fbx-ascii-format": + f_fbxAsciiFormat.Value = true; + flagIndexes.Add(i); + break; case "--filter-with-regex": f_filterWithRegex.Value = true; flagIndexes.Add(i); break; + case "--filter-exclude-mode": + f_filterExcludeMode.Value = true; + flagIndexes.Add(i); + break; + case "--sprite-canvas": + f_spriteExportWithCanvas.Value = true; + flagIndexes.Add(i); + break; case "--il2cpp": f_il2cpp.Value = true; flagIndexes.Add(i); @@ -1546,6 +1601,10 @@ public static void ParseArgs(string[] args) return; } break; + case "--strip-path-prefix": + // Normalize to a directory-style prefix ending with a separator. + o_stripPathPrefix.Value = value.Replace('\\', '/').TrimEnd('/') + "/"; + break; case "--unity-version": try { diff --git a/UnityRiftCLI/ParallelExporter.cs b/UnityRiftCLI/ParallelExporter.cs index 7e55d50..a4c6307 100644 --- a/UnityRiftCLI/ParallelExporter.cs +++ b/UnityRiftCLI/ParallelExporter.cs @@ -78,7 +78,7 @@ public static bool ExportSprite(AssetItem item, string exportPath, out string de var alphaMask = SpriteMaskMode.On; if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) return false; - var image = ((Sprite)item.Asset).GetImage(alphaMask); + var image = ((Sprite)item.Asset).GetImage(alphaMask, CLIOptions.f_spriteExportWithCanvas.Value); if (image != null) { using (image) diff --git a/UnityRiftCLI/Studio.cs b/UnityRiftCLI/Studio.cs index f329979..c7e471d 100644 --- a/UnityRiftCLI/Studio.cs +++ b/UnityRiftCLI/Studio.cs @@ -655,6 +655,14 @@ private static void FilterAssets() ); break; } + + if (CLIOptions.f_filterExcludeMode.Value) + { + var matchedCount = filteredAssets.Count; + filteredAssets = parsedAssetsList.Except(filteredAssets).ToList(); + Logger.Info($"Exclusion mode: dropping {matchedCount} matched asset(s), keeping {filteredAssets.Count}."); + } + parsedAssetsList.Clear(); parsedAssetsList = filteredAssets; } @@ -668,6 +676,26 @@ public static void ExportAssets() var groupOption = CLIOptions.o_groupAssetsBy.Value; var parallelExportCount = CLIOptions.o_maxParallelExportTasks.Value; var toExportAssetDict = new ConcurrentDictionary(); + + // Validate the strip-path-prefix against loaded containers; ignore it if any container + // doesn't start with it (upstream #113). + if (!string.IsNullOrEmpty(CLIOptions.o_stripPathPrefix.Value)) + { + foreach (var asset in parsedAssetsList) + { + if (string.IsNullOrEmpty(asset.Container)) + continue; + if (!asset.Container.StartsWith(CLIOptions.o_stripPathPrefix.Value)) + { + Logger.Warning($"Asset container path \"{asset.Container}\" does not start with the specified path prefix \"{CLIOptions.o_stripPathPrefix.Value}\""); + Logger.Warning("strip-path-prefix option will be ignored."); + CLIOptions.o_stripPathPrefix.Value = ""; + break; + } + } + if (!string.IsNullOrEmpty(CLIOptions.o_stripPathPrefix.Value)) + Logger.Info($"Asset container path prefix \"{CLIOptions.o_stripPathPrefix.Value}\" will be stripped off."); + } var toParallelExportAssetDict = new ConcurrentDictionary(); Parallel.ForEach(parsedAssetsList, asset => { @@ -681,7 +709,17 @@ public static void ExportAssets() case AssetGroupOption.ContainerPathFull: if (!string.IsNullOrEmpty(asset.Container)) { - exportPath = Path.Combine(savePath, Path.GetDirectoryName(asset.Container)); + var containerDir = Path.GetDirectoryName(asset.Container) ?? ""; + // Strip the configured prefix from the container path (upstream #113). + if (!string.IsNullOrEmpty(CLIOptions.o_stripPathPrefix.Value) + && asset.Container.StartsWith(CLIOptions.o_stripPathPrefix.Value)) + { + var stripped = asset.Container.Substring(CLIOptions.o_stripPathPrefix.Value.Length); + containerDir = Path.GetDirectoryName(stripped) ?? ""; + } + exportPath = string.IsNullOrEmpty(containerDir) + ? savePath + : Path.Combine(savePath, containerDir); if (groupOption == AssetGroupOption.ContainerPathFull) { exportPath = Path.Combine(exportPath, Path.GetFileNameWithoutExtension(asset.Container)); diff --git a/UnityRiftFBXWrapper/FbxExporterContext.cs b/UnityRiftFBXWrapper/FbxExporterContext.cs index 910319d..dc22eb4 100644 --- a/UnityRiftFBXWrapper/FbxExporterContext.cs +++ b/UnityRiftFBXWrapper/FbxExporterContext.cs @@ -228,19 +228,19 @@ private void ExportMesh(ImportedFrame rootFrame, List material foreach (var bone in boneList) { + var cluster = IntPtr.Zero; if (bone.Path != null) { var frame = rootFrame.FindFrameByPath(bone.Path); - var boneNode = _frameToNode[frame]; - - var cluster = AsFbxMeshCreateCluster(_pContext, boneNode); - - AsFbxMeshAddCluster(pClusterArray, cluster); - } - else - { - AsFbxMeshAddCluster(pClusterArray, IntPtr.Zero); + // The bone's frame may not exist in the exported hierarchy; skip it + // (add an empty cluster) instead of throwing a NullReferenceException. + if (frame != null) + { + var boneNode = _frameToNode[frame]; + cluster = AsFbxMeshCreateCluster(_pContext, boneNode); + } } + AsFbxMeshAddCluster(pClusterArray, cluster); } } diff --git a/UnityRiftGUI/ExportOptions.Designer.cs b/UnityRiftGUI/ExportOptions.Designer.cs index cdbefec..d48c9ad 100644 --- a/UnityRiftGUI/ExportOptions.Designer.cs +++ b/UnityRiftGUI/ExportOptions.Designer.cs @@ -40,6 +40,7 @@ private void InitializeComponent() this.filenameFormatLabel = new System.Windows.Forms.Label(); this.filenameFormatComboBox = new System.Windows.Forms.ComboBox(); this.exportSpriteWithAlphaMask = new System.Windows.Forms.CheckBox(); + this.exportSpriteWithCanvas = new System.Windows.Forms.CheckBox(); this.openAfterExport = new System.Windows.Forms.CheckBox(); this.restoreExtensionName = new System.Windows.Forms.CheckBox(); this.assetGroupOptions = new System.Windows.Forms.ComboBox(); @@ -134,6 +135,7 @@ private void InitializeComponent() this.groupBox1.Controls.Add(this.filenameFormatLabel); this.groupBox1.Controls.Add(this.filenameFormatComboBox); this.groupBox1.Controls.Add(this.exportSpriteWithAlphaMask); + this.groupBox1.Controls.Add(this.exportSpriteWithCanvas); this.groupBox1.Controls.Add(this.openAfterExport); this.groupBox1.Controls.Add(this.restoreExtensionName); this.groupBox1.Controls.Add(this.assetGroupOptions); @@ -143,7 +145,7 @@ private void InitializeComponent() this.groupBox1.Controls.Add(this.converttexture); this.groupBox1.Location = new System.Drawing.Point(12, 13); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(316, 303); + this.groupBox1.Size = new System.Drawing.Size(316, 340); this.groupBox1.TabIndex = 1; this.groupBox1.TabStop = false; this.groupBox1.Text = "Export"; @@ -248,7 +250,17 @@ private void InitializeComponent() this.exportSpriteWithAlphaMask.TabIndex = 9; this.exportSpriteWithAlphaMask.Text = "Export sprites with alpha mask applied"; this.exportSpriteWithAlphaMask.UseVisualStyleBackColor = true; - // + // + // exportSpriteWithCanvas + // + this.exportSpriteWithCanvas.AutoSize = true; + this.exportSpriteWithCanvas.Location = new System.Drawing.Point(6, 314); + this.exportSpriteWithCanvas.Name = "exportSpriteWithCanvas"; + this.exportSpriteWithCanvas.Size = new System.Drawing.Size(205, 17); + this.exportSpriteWithCanvas.TabIndex = 16; + this.exportSpriteWithCanvas.Text = "Export sprites at full canvas size"; + this.exportSpriteWithCanvas.UseVisualStyleBackColor = true; + // // openAfterExport // this.openAfterExport.AutoSize = true; @@ -889,6 +901,7 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox exportAllUvsAsDiffuseMaps; private System.Windows.Forms.ToolTip optionTooltip; private System.Windows.Forms.CheckBox exportSpriteWithAlphaMask; + private System.Windows.Forms.CheckBox exportSpriteWithCanvas; private System.Windows.Forms.RadioButton towebp; private System.Windows.Forms.GroupBox l2dGroupBox; private System.Windows.Forms.CheckBox l2dForceBezierCheckBox; diff --git a/UnityRiftGUI/ExportOptions.cs b/UnityRiftGUI/ExportOptions.cs index 8330a46..3ce226f 100644 --- a/UnityRiftGUI/ExportOptions.cs +++ b/UnityRiftGUI/ExportOptions.cs @@ -20,6 +20,7 @@ public ExportOptions() restoreExtensionName.Checked = Properties.Settings.Default.restoreExtensionName; converttexture.Checked = Properties.Settings.Default.convertTexture; exportSpriteWithAlphaMask.Checked = Properties.Settings.Default.exportSpriteWithMask; + exportSpriteWithCanvas.Checked = Properties.Settings.Default.spriteExportWithCanvas; convertAudio.Checked = Properties.Settings.Default.convertAudio; var defaultImageType = Properties.Settings.Default.convertType.ToString(); ((RadioButton)panel1.Controls.Cast().First(x => x.Text == defaultImageType)).Checked = true; @@ -50,6 +51,7 @@ private void OKbutton_Click(object sender, EventArgs e) Properties.Settings.Default.restoreExtensionName = restoreExtensionName.Checked; Properties.Settings.Default.convertTexture = converttexture.Checked; Properties.Settings.Default.exportSpriteWithMask = exportSpriteWithAlphaMask.Checked; + Properties.Settings.Default.spriteExportWithCanvas = exportSpriteWithCanvas.Checked; Properties.Settings.Default.convertAudio = convertAudio.Checked; var checkedImageType = (RadioButton)panel1.Controls.Cast().First(x => ((RadioButton)x).Checked); Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), checkedImageType.Text); diff --git a/UnityRiftGUI/ParallelExport.cs b/UnityRiftGUI/ParallelExport.cs index ae85c8f..0188061 100644 --- a/UnityRiftGUI/ParallelExport.cs +++ b/UnityRiftGUI/ParallelExport.cs @@ -77,7 +77,7 @@ public static bool ExportSprite(AssetItem item, string exportPath, out string de var spriteMaskMode = Properties.Settings.Default.exportSpriteWithMask ? SpriteMaskMode.Export : SpriteMaskMode.Off; if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) return false; - var image = ((Sprite)item.Asset).GetImage(spriteMaskMode: spriteMaskMode); + var image = ((Sprite)item.Asset).GetImage(spriteMaskMode: spriteMaskMode, spriteWithCanvas: Properties.Settings.Default.spriteExportWithCanvas); if (image != null) { using (image) diff --git a/UnityRiftGUI/Properties/Settings.Designer.cs b/UnityRiftGUI/Properties/Settings.Designer.cs index 800dfab..01d15f4 100644 --- a/UnityRiftGUI/Properties/Settings.Designer.cs +++ b/UnityRiftGUI/Properties/Settings.Designer.cs @@ -154,7 +154,19 @@ public bool exportSpriteWithMask { this["exportSpriteWithMask"] = value; } } - + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool spriteExportWithCanvas { + get { + return ((bool)(this["spriteExportWithCanvas"])); + } + set { + this["spriteExportWithCanvas"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("MonoBehaviour")] diff --git a/UnityRiftGUI/Properties/Settings.settings b/UnityRiftGUI/Properties/Settings.settings index 1e1c61e..ea20730 100644 --- a/UnityRiftGUI/Properties/Settings.settings +++ b/UnityRiftGUI/Properties/Settings.settings @@ -35,6 +35,9 @@ True + + False + MonoBehaviour diff --git a/UnityRiftUtility/SpriteHelper.cs b/UnityRiftUtility/SpriteHelper.cs index 3681eed..9a4d35a 100644 --- a/UnityRiftUtility/SpriteHelper.cs +++ b/UnityRiftUtility/SpriteHelper.cs @@ -21,13 +21,13 @@ public enum SpriteMaskMode public static class SpriteHelper { - public static Image GetImage(this Sprite m_Sprite, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On) + public static Image GetImage(this Sprite m_Sprite, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On, bool spriteWithCanvas = false) { if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlas)) { if (m_SpriteAtlas.m_RenderDataMap.TryGetValue(m_Sprite.m_RenderDataKey, out var spriteAtlasData) && spriteAtlasData.texture.TryGet(out var m_Texture2D)) { - return CutImage(m_Sprite, m_Texture2D, spriteAtlasData.textureRect, spriteAtlasData.textureRectOffset, spriteAtlasData.downscaleMultiplier, spriteAtlasData.settingsRaw); + return CutImageWithCanvas(m_Sprite, m_Texture2D, spriteAtlasData.textureRect, spriteAtlasData.textureRectOffset, spriteAtlasData.downscaleMultiplier, spriteAtlasData.settingsRaw, spriteWithCanvas); } } else @@ -37,9 +37,9 @@ public static Image GetImage(this Sprite m_Sprite, SpriteMaskMode sprite Image tex = null; if (spriteMaskMode != SpriteMaskMode.MaskOnly) { - tex = CutImage(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw); + tex = CutImageWithCanvas(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw, spriteWithCanvas); } - var alphaTex = CutImage(m_Sprite, m_AlphaTexture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw); + var alphaTex = CutImageWithCanvas(m_Sprite, m_AlphaTexture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw, spriteWithCanvas); switch (spriteMaskMode) { @@ -55,12 +55,32 @@ public static Image GetImage(this Sprite m_Sprite, SpriteMaskMode sprite } else if (m_Sprite.m_RD.texture.TryGet(out m_Texture2D)) { - return CutImage(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw); + return CutImageWithCanvas(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw, spriteWithCanvas); } } return null; } + // Optionally place the cropped sprite onto a full-size canvas (the sprite's authored + // m_Rect), so the export keeps the sprite's original dimensions with transparent padding + // instead of the tight cropped region (upstream #115). Default off = cropped behavior. + private static Image CutImageWithCanvas(Sprite m_Sprite, Texture2D m_Texture2D, Rectf textureRect, Vector2 textureRectOffset, float downscaleMultiplier, SpriteSettings settingsRaw, bool spriteWithCanvas = false) + { + var cropped = CutImage(m_Sprite, m_Texture2D, textureRect, textureRectOffset, downscaleMultiplier, settingsRaw); + if (cropped == null) + return null; + if (!spriteWithCanvas) + return cropped; + + var canvas = new Image((int)MathF.Floor(m_Sprite.m_Rect.width), (int)MathF.Floor(m_Sprite.m_Rect.height)); + canvas.Mutate(ctx => ctx.DrawImage( + cropped, + new Point((int)MathF.Floor(textureRectOffset.X), (int)MathF.Floor(m_Sprite.m_Rect.height - textureRectOffset.Y - cropped.Height)), + 1f)); + cropped.Dispose(); + return canvas; + } + private static void ApplyRGBMask(this Image tex, Image texMask, bool isPreview = false) { using (texMask) diff --git a/UnityRiftUtility/UnityRiftUtility.csproj b/UnityRiftUtility/UnityRiftUtility.csproj index 4ae0fab..395c4da 100644 --- a/UnityRiftUtility/UnityRiftUtility.csproj +++ b/UnityRiftUtility/UnityRiftUtility.csproj @@ -15,12 +15,12 @@ - - + + - + 0.17.0