From 1344b34d08d79aa58c531477e9f013c6e5f66b26 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Fri, 25 Jul 2025 22:09:24 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=B6=E9=95=BF?= =?UTF-8?q?=E5=8F=82=E6=95=B00=E5=80=BC=E5=88=A4=E6=96=AD=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomFFmpegExporterViewModel.cs | 20 +++++++++++-------- .../Exporters/FFmpegVideoExporterViewModel.cs | 20 +++++++++++-------- .../FrameSequenceExporterViewModel.cs | 20 +++++++++++-------- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs index 6dc050b..7dcc382 100644 --- a/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs +++ b/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs @@ -92,7 +92,9 @@ namespace SpineViewer.ViewModels.Exporters var output = Path.Combine(_outputDir!, filename); if (_autoResolution) SetAutoResolutionAnimated(exporter, spines); - exporter.Duration = _duration >= 0 ? _duration : spines.Select(sp => sp.GetAnimationMaxDuration()).DefaultIfEmpty(0).Max(); + + // 如果时长是一个负数值则使用所有动画时长的最大值 + exporter.Duration = _duration < 0 ? spines.Select(sp => sp.GetAnimationMaxDuration()).DefaultIfEmpty(0).Max() : _duration; exporter.ProgressReporter = (total, done, text) => { @@ -119,12 +121,7 @@ namespace SpineViewer.ViewModels.Exporters { // 统计总帧数 int totalFrameCount = 0; - if (_duration > 0) - { - exporter.Duration = _duration; - totalFrameCount = exporter.GetFrameCount() * spines.Length; - } - else + if (_duration < 0) { foreach (var sp in spines) { @@ -132,6 +129,11 @@ namespace SpineViewer.ViewModels.Exporters totalFrameCount += exporter.GetFrameCount(); } } + else + { + exporter.Duration = _duration; + totalFrameCount = exporter.GetFrameCount() * spines.Length; + } pr.Total = totalFrameCount; pr.Done = 0; @@ -154,7 +156,9 @@ namespace SpineViewer.ViewModels.Exporters } if (_autoResolution) SetAutoResolutionAnimated(exporter, sp); - if (_duration <= 0) exporter.Duration = sp.GetAnimationMaxDuration(); + + // 如果时长是负数则需要每次都设置成动画的时长值, 否则前面统计帧数时已经设置过时长值 + if (_duration < 0) exporter.Duration = sp.GetAnimationMaxDuration(); var filename = $"{sp.Name}_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{_fps}{FormatSuffix}"; var output = Path.Combine(_outputDir ?? sp.AssetsDir, filename); diff --git a/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs index 97468ee..d85d751 100644 --- a/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs +++ b/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs @@ -83,7 +83,9 @@ namespace SpineViewer.ViewModels.Exporters var output = Path.Combine(_outputDir!, filename); if (_autoResolution) SetAutoResolutionAnimated(exporter, spines); - exporter.Duration = _duration >= 0 ? _duration : spines.Select(sp => sp.GetAnimationMaxDuration()).DefaultIfEmpty(0).Max(); + + // 如果时长是一个负数值则使用所有动画时长的最大值 + exporter.Duration = _duration < 0 ? spines.Select(sp => sp.GetAnimationMaxDuration()).DefaultIfEmpty(0).Max() : _duration; exporter.ProgressReporter = (total, done, text) => { @@ -110,12 +112,7 @@ namespace SpineViewer.ViewModels.Exporters { // 统计总帧数 int totalFrameCount = 0; - if (_duration > 0) - { - exporter.Duration = _duration; - totalFrameCount = exporter.GetFrameCount() * spines.Length; - } - else + if (_duration < 0) { foreach (var sp in spines) { @@ -123,6 +120,11 @@ namespace SpineViewer.ViewModels.Exporters totalFrameCount += exporter.GetFrameCount(); } } + else + { + exporter.Duration = _duration; + totalFrameCount = exporter.GetFrameCount() * spines.Length; + } pr.Total = totalFrameCount; pr.Done = 0; @@ -145,7 +147,9 @@ namespace SpineViewer.ViewModels.Exporters } if (_autoResolution) SetAutoResolutionAnimated(exporter, sp); - if (_duration <= 0) exporter.Duration = sp.GetAnimationMaxDuration(); + + // 如果时长是负数则需要每次都设置成动画的时长值, 否则前面统计帧数时已经设置过时长值 + if (_duration < 0) exporter.Duration = sp.GetAnimationMaxDuration(); var filename = $"{sp.Name}_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{_fps}{FormatSuffix}"; var output = Path.Combine(_outputDir ?? sp.AssetsDir, filename); diff --git a/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs index 68fcdd6..cb547d5 100644 --- a/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs +++ b/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs @@ -56,7 +56,9 @@ namespace SpineViewer.ViewModels.Exporters var output = Path.Combine(_outputDir!, folderName); if (_autoResolution) SetAutoResolutionAnimated(exporter, spines); - exporter.Duration = _duration >= 0 ? _duration : spines.Select(sp => sp.GetAnimationMaxDuration()).DefaultIfEmpty(0).Max(); + + // 如果时长是一个负数值则使用所有动画时长的最大值 + exporter.Duration = _duration < 0 ? spines.Select(sp => sp.GetAnimationMaxDuration()).DefaultIfEmpty(0).Max() : _duration; exporter.ProgressReporter = (total, done, text) => { @@ -83,12 +85,7 @@ namespace SpineViewer.ViewModels.Exporters { // 统计总帧数 int totalFrameCount = 0; - if (_duration > 0) - { - exporter.Duration = _duration; - totalFrameCount = exporter.GetFrameCount() * spines.Length; - } - else + if (_duration < 0) { foreach (var sp in spines) { @@ -96,6 +93,11 @@ namespace SpineViewer.ViewModels.Exporters totalFrameCount += exporter.GetFrameCount(); } } + else + { + exporter.Duration = _duration; + totalFrameCount = exporter.GetFrameCount() * spines.Length; + } pr.Total = totalFrameCount; pr.Done = 0; @@ -118,7 +120,9 @@ namespace SpineViewer.ViewModels.Exporters } if (_autoResolution) SetAutoResolutionAnimated(exporter, sp); - if (_duration <= 0) exporter.Duration = sp.GetAnimationMaxDuration(); + + // 如果时长是负数则需要每次都设置成动画的时长值, 否则前面统计帧数时已经设置过时长值 + if (_duration < 0) exporter.Duration = sp.GetAnimationMaxDuration(); var folderName = $"{sp.Name}_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{_fps}"; var output = Path.Combine(_outputDir ?? sp.AssetsDir, folderName); From 390416df062b97b60f5b9371b61a852f42b9af00 Mon Sep 17 00:00:00 2001 From: ashlen Date: Fri, 25 Jul 2025 17:19:16 +0200 Subject: [PATCH 02/10] Add CLI --- SpineViewer/App.xaml.cs | 10 +++ SpineViewer/CLI.cs | 187 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 SpineViewer/CLI.cs diff --git a/SpineViewer/App.xaml.cs b/SpineViewer/App.xaml.cs index 9215dda..9027673 100644 --- a/SpineViewer/App.xaml.cs +++ b/SpineViewer/App.xaml.cs @@ -1,4 +1,5 @@ using NLog; +using System.Runtime.InteropServices; using SpineViewer.Views; using System.Collections.Frozen; using System.Configuration; @@ -7,6 +8,7 @@ using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Windows; +using SpineViewerCLI; namespace SpineViewer { @@ -15,6 +17,9 @@ namespace SpineViewer /// public partial class App : Application { + [DllImport("kernel32.dll")] + private static extern bool AttachConsole(int dwProcessId); + public static string Version => Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion; private static readonly Logger _logger; @@ -61,6 +66,11 @@ namespace SpineViewer protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); + if (e.Args.Length > 0) + { + AttachConsole(-1); + CLI.CliMain(e.Args); + } var dict = new ResourceDictionary(); diff --git a/SpineViewer/CLI.cs b/SpineViewer/CLI.cs new file mode 100644 index 0000000..fc13d49 --- /dev/null +++ b/SpineViewer/CLI.cs @@ -0,0 +1,187 @@ + +using System.Globalization; +using System.IO; +using SFML.Graphics; +using Spine; +using Spine.Exporters; +using SpineViewer.Extensions; + +namespace SpineViewerCLI +{ + public class CLI + { + const string USAGE = @" +usage: SpineExporter.exe [--skel PATH] [--atlas PATH] [--output PATH] [--animation STR] [--pma] [--fps INT] [--loop] [--crf INT] [--width INT] [--height INT] [--centerx INT] [--centery INT] [--zoom FLOAT] [--speed FLOAT] [--color HEX] [--quiet] + +options: + --skel PATH Path to the .skel file + --atlas PATH Path to the .atlas file, default searches in the skel file directory + --output PATH Output file path + --animation STR Animation name + --pma Use premultiplied alpha, default false + --fps INT Frames per second, default 24 + --loop Whether to loop the animation, default false + --crf INT Constant Rate Factor i.e. video quality, from 0 (lossless) to 51 (worst), default 23 + --width INT Output width, default 512 + --height INT Output height, default 512 + --centerx INT Center X offset, default automatically finds bounds + --centery INT Center Y offset, default automatically finds bounds + --zoom FLOAT Zoom level, default 1.0 + --speed FLOAT Speed of animation, default 1.0 + --color HEX Background color as a hex RGBA color, default 000000ff (opaque black) + --quiet Removes console progress log, default false +"; + + public static void CliMain(string[] args) + { + + string? skelPath = null; + string? atlasPath = null; + string? output = null; + string? animation = null; + bool pma = false; + uint fps = 24; + bool loop = false; + int crf = 23; + uint? width = null; + uint? height = null; + int? centerx = null; + int? centery = null; + float zoom = 1; + float speed = 1; + Color backgroundColor = Color.Black; + bool quiet = false; + + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--help": + Console.Write(USAGE); + Environment.Exit(0); + break; + case "--skel": + skelPath = args[++i]; + break; + case "--atlas": + atlasPath = args[++i]; + break; + case "--output": + output = args[++i]; + break; + case "--animation": + animation = args[++i]; + break; + case "--pma": + pma = true; + break; + case "--fps": + fps = uint.Parse(args[++i]); + break; + case "--loop": + loop = true; + break; + case "--crf": + crf = int.Parse(args[++i]); + break; + case "--width": + width = uint.Parse(args[++i]); + break; + case "--height": + height = uint.Parse(args[++i]); + break; + case "--centerx": + centerx = int.Parse(args[++i]); + break; + case "--centery": + centery = int.Parse(args[++i]); + break; + case "--zoom": + zoom = float.Parse(args[++i]); + break; + case "--speed": + speed = float.Parse(args[++i]); + break; + case "--color": + backgroundColor = new Color(uint.Parse(args[++i], NumberStyles.HexNumber)); + break; + case "--quiet": + quiet = true; + break; + default: + Console.Error.WriteLine($"Unknown argument: {args[i]}"); + Environment.Exit(2); + break; + } + } + + if (string.IsNullOrEmpty(skelPath)) + { + Console.Error.WriteLine("Missing --skel"); + Environment.Exit(2); + } + if (string.IsNullOrEmpty(output)) + { + Console.Error.WriteLine("Missing --output"); + Environment.Exit(2); + } + if (!Enum.TryParse(Path.GetExtension(output).TrimStart('.'), true, out var videoFormat)) + { + var validExtensions = string.Join(", ", Enum.GetNames(typeof(FFmpegVideoExporter.VideoFormat))); + Console.Error.WriteLine($"Invalid output extension. Supported formats are: {validExtensions}"); + Environment.Exit(2); + } + + var sp = new SpineObject(skelPath, atlasPath); + sp.UsePma = pma; + + if (string.IsNullOrEmpty(animation)) + { + var availableAnimations = string.Join(", ", sp.Data.Animations); + Console.Error.WriteLine($"Missing --animation. Available animations for {sp.Name}: {availableAnimations}"); + Environment.Exit(2); + } + var trackEntry = sp.AnimationState.SetAnimation(0, animation, loop); + sp.Update(0); + + FFmpegVideoExporter exporter; + if (width is uint w && height is uint h && centerx is int cx && centery is int cy) + { + exporter = new FFmpegVideoExporter(w, h) + { + Center = (cx, cy), + Size = (w / zoom, -h / zoom), + }; + } + else + { + var rect = sp.GetAnimationBounds(); + var bounds = new FloatRect((float)rect.X, (float)rect.Y, (float)rect.Width, (float)rect.Height).GetCanvasBounds(new(width ?? 512, height ?? 512)); + + exporter = new FFmpegVideoExporter(width ?? (uint)Math.Ceiling(bounds.Width), height ?? (uint)Math.Ceiling(bounds.Height)) + { + Center = bounds.Position + bounds.Size / 2, + Size = (bounds.Width, -bounds.Height), + }; + } + exporter.Duration = trackEntry.Animation.Duration; + exporter.Fps = fps; + exporter.Format = videoFormat; + exporter.Loop = loop; + exporter.Crf = crf; + exporter.Speed = speed; + exporter.BackgroundColor = backgroundColor; + + if (!quiet) + exporter.ProgressReporter = (total, done, text) => Console.Write($"\r{text}"); + + using var cts = new CancellationTokenSource(); + exporter.Export(output, cts.Token, sp); + + if (!quiet) + Console.WriteLine(); + + Environment.Exit(0); + } + } +} \ No newline at end of file From 86bcb079b05df9832883d1aedc3d0f9db00a8dfb Mon Sep 17 00:00:00 2001 From: ashlen Date: Fri, 25 Jul 2025 18:56:55 +0200 Subject: [PATCH 03/10] Move to its own project --- SpineViewer/App.xaml.cs | 10 --- .../SpineViewerCLI.cs | 66 +++++++++++++++++-- SpineViewerCLI/SpineViewerCLI.csproj | 15 +++++ 3 files changed, 75 insertions(+), 16 deletions(-) rename SpineViewer/CLI.cs => SpineViewerCLI/SpineViewerCLI.cs (73%) create mode 100644 SpineViewerCLI/SpineViewerCLI.csproj diff --git a/SpineViewer/App.xaml.cs b/SpineViewer/App.xaml.cs index 9027673..9215dda 100644 --- a/SpineViewer/App.xaml.cs +++ b/SpineViewer/App.xaml.cs @@ -1,5 +1,4 @@ using NLog; -using System.Runtime.InteropServices; using SpineViewer.Views; using System.Collections.Frozen; using System.Configuration; @@ -8,7 +7,6 @@ using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Windows; -using SpineViewerCLI; namespace SpineViewer { @@ -17,9 +15,6 @@ namespace SpineViewer /// public partial class App : Application { - [DllImport("kernel32.dll")] - private static extern bool AttachConsole(int dwProcessId); - public static string Version => Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion; private static readonly Logger _logger; @@ -66,11 +61,6 @@ namespace SpineViewer protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); - if (e.Args.Length > 0) - { - AttachConsole(-1); - CLI.CliMain(e.Args); - } var dict = new ResourceDictionary(); diff --git a/SpineViewer/CLI.cs b/SpineViewerCLI/SpineViewerCLI.cs similarity index 73% rename from SpineViewer/CLI.cs rename to SpineViewerCLI/SpineViewerCLI.cs index fc13d49..4d18f54 100644 --- a/SpineViewer/CLI.cs +++ b/SpineViewerCLI/SpineViewerCLI.cs @@ -1,10 +1,9 @@ - using System.Globalization; using System.IO; using SFML.Graphics; +using SFML.System; using Spine; using Spine.Exporters; -using SpineViewer.Extensions; namespace SpineViewerCLI { @@ -32,7 +31,7 @@ options: --quiet Removes console progress log, default false "; - public static void CliMain(string[] args) + public static void Main(string[] args) { string? skelPath = null; @@ -155,9 +154,7 @@ options: } else { - var rect = sp.GetAnimationBounds(); - var bounds = new FloatRect((float)rect.X, (float)rect.Y, (float)rect.Width, (float)rect.Height).GetCanvasBounds(new(width ?? 512, height ?? 512)); - + var bounds = GetFloatRectCanvasBounds(GetSpineObjectAnimationBounds(sp, fps), new(width ?? 512, height ?? 512)); exporter = new FFmpegVideoExporter(width ?? (uint)Math.Ceiling(bounds.Width), height ?? (uint)Math.Ceiling(bounds.Height)) { Center = bounds.Position + bounds.Size / 2, @@ -183,5 +180,62 @@ options: Environment.Exit(0); } + + public static SpineObject CopySpineObject(SpineObject sp) + { + var spineObject = new SpineObject(sp, true); + foreach (var tr in sp.AnimationState.IterTracks().Where(t => t is not null)) + { + var t = spineObject.AnimationState.SetAnimation(tr!.TrackIndex, tr.Animation, tr.Loop); + } + spineObject.Update(0); + return spineObject; + } + + static FloatRect GetSpineObjectBounds(SpineObject sp) + { + sp.Skeleton.GetBounds(out var x, out var y, out var w, out var h); + return new(x, y, Math.Max(w, 1e-6f), Math.Max(h, 1e-6f)); + } + static FloatRect FloatRectUnion(FloatRect a, FloatRect b) + { + float left = Math.Min(a.Left, b.Left); + float top = Math.Min(a.Top, b.Top); + float right = Math.Max(a.Left + a.Width, b.Left + b.Width); + float bottom = Math.Max(a.Top + a.Height, b.Top + b.Height); + return new FloatRect(left, top, right - left, bottom - top); + } + static FloatRect GetSpineObjectAnimationBounds(SpineObject sp, float fps = 10) + { + sp = CopySpineObject(sp); + var bounds = GetSpineObjectBounds(sp); + var maxDuration = sp.AnimationState.IterTracks().Select(t => t?.Animation.Duration ?? 0).DefaultIfEmpty(0).Max(); + sp.Update(0); + for (float tick = 0, delta = 1 / fps; tick < maxDuration; tick += delta) + { + bounds = FloatRectUnion(bounds, GetSpineObjectBounds(sp)); + sp.Update(delta); + } + return bounds; + } + static FloatRect GetFloatRectCanvasBounds(FloatRect rect, Vector2u resolution) + { + float sizeW = rect.Width; + float sizeH = rect.Height; + float innerW = resolution.X; + float innerH = resolution.Y; + var scale = Math.Max(Math.Abs(sizeW / innerW), Math.Abs(sizeH / innerH)); + var scaleW = scale * Math.Sign(sizeW); + var scaleH = scale * Math.Sign(sizeH); + + innerW *= scaleW; + innerH *= scaleH; + + var x = rect.Left - (innerW - sizeW) / 2; + var y = rect.Top - (innerH - sizeH) / 2; + var w = resolution.X * scaleW; + var h = resolution.Y * scaleH; + return new(x, y, w, h); + } } } \ No newline at end of file diff --git a/SpineViewerCLI/SpineViewerCLI.csproj b/SpineViewerCLI/SpineViewerCLI.csproj new file mode 100644 index 0000000..3bdb4d3 --- /dev/null +++ b/SpineViewerCLI/SpineViewerCLI.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0-windows7.0 + enable + enable + + + + + + + + From 1d7a4027496f526d30b0acd7f2e9b565db11ff82 Mon Sep 17 00:00:00 2001 From: ww-rm <43590561+ww-rm@users.noreply.github.com> Date: Sat, 26 Jul 2025 08:49:34 +0800 Subject: [PATCH 04/10] Update SpineViewerCLI.csproj --- SpineViewerCLI/SpineViewerCLI.csproj | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/SpineViewerCLI/SpineViewerCLI.csproj b/SpineViewerCLI/SpineViewerCLI.csproj index 3bdb4d3..dbb905c 100644 --- a/SpineViewerCLI/SpineViewerCLI.csproj +++ b/SpineViewerCLI/SpineViewerCLI.csproj @@ -1,10 +1,14 @@ - Exe - net8.0-windows7.0 - enable enable + enable + x64 + net8.0-windows + $(SolutionDir)out + false + 0.0.1 + Exe From ddd3e946988f91785c5baf31725d8c97f5730ec3 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Sat, 26 Jul 2025 22:28:15 +0800 Subject: [PATCH 05/10] fix wrong text --- SpineViewerCLI/SpineViewerCLI.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SpineViewerCLI/SpineViewerCLI.cs b/SpineViewerCLI/SpineViewerCLI.cs index 4d18f54..2c91c5e 100644 --- a/SpineViewerCLI/SpineViewerCLI.cs +++ b/SpineViewerCLI/SpineViewerCLI.cs @@ -10,7 +10,7 @@ namespace SpineViewerCLI public class CLI { const string USAGE = @" -usage: SpineExporter.exe [--skel PATH] [--atlas PATH] [--output PATH] [--animation STR] [--pma] [--fps INT] [--loop] [--crf INT] [--width INT] [--height INT] [--centerx INT] [--centery INT] [--zoom FLOAT] [--speed FLOAT] [--color HEX] [--quiet] +usage: SpineViewerCLI.exe [--skel PATH] [--atlas PATH] [--output PATH] [--animation STR] [--pma] [--fps INT] [--loop] [--crf INT] [--width INT] [--height INT] [--centerx INT] [--centery INT] [--zoom FLOAT] [--speed FLOAT] [--color HEX] [--quiet] options: --skel PATH Path to the .skel file @@ -33,7 +33,6 @@ options: public static void Main(string[] args) { - string? skelPath = null; string? atlasPath = null; string? output = null; From fa00f0064ee212f4ee1b99a24ed1fd37942721bd Mon Sep 17 00:00:00 2001 From: ww-rm Date: Sat, 26 Jul 2025 22:28:25 +0800 Subject: [PATCH 06/10] remove warning --- SpineViewerCLI/SpineViewerCLI.csproj | 32 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/SpineViewerCLI/SpineViewerCLI.csproj b/SpineViewerCLI/SpineViewerCLI.csproj index dbb905c..7ccfe44 100644 --- a/SpineViewerCLI/SpineViewerCLI.csproj +++ b/SpineViewerCLI/SpineViewerCLI.csproj @@ -1,19 +1,23 @@ - - enable - enable - x64 - net8.0-windows - $(SolutionDir)out - false - 0.0.1 - Exe - + + enable + enable + x64 + net8.0-windows + $(SolutionDir)out + false + 0.0.1 + Exe + - - - - + + $(NoWarn);NETSDK1206 + + + + + + From 2eded25c0320194c827524206e697b7d79467519 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Sat, 26 Jul 2025 22:28:37 +0800 Subject: [PATCH 07/10] add cli project --- SpineViewer.sln | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SpineViewer.sln b/SpineViewer.sln index 70dfac9..38401a8 100644 --- a/SpineViewer.sln +++ b/SpineViewer.sln @@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SFMLRenderer", "SFMLRendere EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLog.Windows.Wpf", "NLog.Windows.Wpf\NLog.Windows.Wpf.csproj", "{8EAB9780-9DBA-A755-6C73-0CE5AC5CE557}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineViewerCLI", "SpineViewerCLI\SpineViewerCLI.csproj", "{6BC146FC-F81E-65DA-EDDF-5734DBCCB628}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -86,6 +88,10 @@ Global {8EAB9780-9DBA-A755-6C73-0CE5AC5CE557}.Debug|x64.Build.0 = Debug|x64 {8EAB9780-9DBA-A755-6C73-0CE5AC5CE557}.Release|x64.ActiveCfg = Release|x64 {8EAB9780-9DBA-A755-6C73-0CE5AC5CE557}.Release|x64.Build.0 = Release|x64 + {6BC146FC-F81E-65DA-EDDF-5734DBCCB628}.Debug|x64.ActiveCfg = Debug|x64 + {6BC146FC-F81E-65DA-EDDF-5734DBCCB628}.Debug|x64.Build.0 = Debug|x64 + {6BC146FC-F81E-65DA-EDDF-5734DBCCB628}.Release|x64.ActiveCfg = Release|x64 + {6BC146FC-F81E-65DA-EDDF-5734DBCCB628}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 0a5432bb3057d7dc64337636f7e08bea8b3e079b Mon Sep 17 00:00:00 2001 From: ww-rm Date: Sat, 26 Jul 2025 22:32:15 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0cli=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=9A=84=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dotnet-desktop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index fc09697..cc36120 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -13,6 +13,7 @@ jobs: runs-on: windows-latest env: PROJECT_NAME: SpineViewer + PROJ_CLI_NAME: SpineViewerCLI steps: - name: Checkout code @@ -54,11 +55,13 @@ jobs: shell: pwsh run: | dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc false -o "publish\$env:PROJECT_NAME-$env:VERSION" + dotnet publish "$env:PROJECT_NAME\$env:PROJ_CLI_NAME.csproj" -c Release -r win-x64 --sc false -o "publish\$env:PROJECT_NAME-$env:VERSION" - name: Publish SelfContained version shell: pwsh run: | dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained" + dotnet publish "$env:PROJECT_NAME\$env:PROJ_CLI_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained" - name: Create release directory shell: pwsh From 89c31d7c77f5227d06af957b6939c0c8fe32897f Mon Sep 17 00:00:00 2001 From: ww-rm Date: Sat, 26 Jul 2025 22:58:27 +0800 Subject: [PATCH 09/10] add contributing --- CONTRIBUTING.md | 41 +++++++++++++++++++++++++++++++++++++++++ SpineViewer.sln | 1 + 2 files changed, 42 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3f382ae --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# CONTRIBUTING + +## 仓库分支 + +仓库目前包含 4 个分支: + +- `main`: 默认分支, 也是项目最新版的发布用分支 +- `dev/wpf`: WPF 版本开发分支 +- `release/wf`: Winforms 旧版本发布分支 (已弃用, 仅进行 bug 修复) +- `dev/wf`: Winforms 旧版本开发分支 (已弃用, 仅进行 bug 修复) + +仓库的每个发布分支都有对应的开发分支 `dev/*`, **在进行贡献和推送时请在开发分支上进行**, 待开发分支上审核完毕进行必要的确认 (例如版本号的更新) 后, 再从开发分支向对应的发布分支发起 pr, 合并后将会通过 Actions 进行自动生成和发布. + +## 仓库结构 + +仓库目前包含两个可执行文件项目, 分别是: + +- `SpineViewer.csproj` +- `SpineViewerCLI.csproj` + +前者为仓库主要项目, 提供一个预览操作 Spine 模型文件的 UI 界面, 后者基于社区贡献进行开发, 提供一些便捷的 CLI 功能, 从而可以对模型文件进行一些批量操作. + +除此之外其余项目均为一些基础功能库, 为以上两个项目提供必要的功能支持. 原则上 UI 项目和 CLI 项目二者独立互不引用, 仅引用相同的基础功能库, 以保证整个仓库的层次结构清晰便于维护. + +## 如何贡献 + +对于一些小改动, 例如: + +- 某些文件内的 bug 修复 (例如一些逻辑上的错误) +- 已有功能的扩展性增强 (例如在已有代码逻辑结构上扩充某些功能字段) +- 其他可能的对**已有功能**的修复改进 + +可以直接 fork 修改后向开发分支发起 pr, 经 review 无问题后可直接合并. + +对于较大的改动, 例如: + +- 新增某些代码文件 (例如需要添加一些全新的类) +- 添加一些全新的逻辑或者功能代码 (例如在自行车上加装发动机) +- 其他可能影响项目代码逻辑结构的改动 + +这些改动请先提 Issue, 进行必要性讨论, 以及确认新功能的引入方式, 请不要直接将这些可能的破坏性改动发起 pr. diff --git a/SpineViewer.sln b/SpineViewer.sln index 38401a8..2b6f92e 100644 --- a/SpineViewer.sln +++ b/SpineViewer.sln @@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt .editorconfig = .editorconfig .gitignore = .gitignore CHANGELOG.md = CHANGELOG.md + CONTRIBUTING.md = CONTRIBUTING.md README.en.md = README.en.md README.md = README.md EndProjectSection From 93b806dccdfa7d72b299f0059e9d62ddf7d2b335 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Sat, 26 Jul 2025 22:59:32 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=87=B3v0.15.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpineViewer/SpineViewer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpineViewer/SpineViewer.csproj b/SpineViewer/SpineViewer.csproj index 365abbc..2318fd8 100644 --- a/SpineViewer/SpineViewer.csproj +++ b/SpineViewer/SpineViewer.csproj @@ -7,7 +7,7 @@ net8.0-windows $(SolutionDir)out false - 0.15.6 + 0.15.7 WinExe true