增加单独的参数拷贝方式
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Spine.Interfaces;
|
||||
using SpineViewer.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -13,6 +14,15 @@ namespace SpineViewer.Models
|
||||
{
|
||||
public class SpineObjectConfigModel
|
||||
{
|
||||
public class TrackConfigModel
|
||||
{
|
||||
public string AnimationName { get; set; } = "";
|
||||
|
||||
public float TimeScale { get; set; } = 1f;
|
||||
|
||||
public float Alpha { get; set; } = 1f;
|
||||
}
|
||||
|
||||
public bool UsePma { get; set; }
|
||||
|
||||
public string Physics { get; set; } = ISkeleton.Physics.Update.ToString();
|
||||
@@ -57,14 +67,6 @@ namespace SpineViewer.Models
|
||||
|
||||
public bool DebugClippings { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TrackConfigModel
|
||||
{
|
||||
public string AnimationName { get; set; } = "";
|
||||
|
||||
public float TimeScale { get; set; } = 1f;
|
||||
|
||||
public float Alpha { get; set; } = 1f;
|
||||
public override string ToString() => JsonHelper.Serialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,8 @@ namespace SpineViewer.Models
|
||||
|
||||
public event EventHandler<TrackPropertyChangedEventArgs>? TrackPropertyChanged;
|
||||
|
||||
#region 参数面板实现
|
||||
|
||||
public SpineVersion Version => _spineObject.Version;
|
||||
|
||||
public string AssetsDir => _spineObject.AssetsDir;
|
||||
@@ -407,6 +409,8 @@ namespace SpineViewer.Models
|
||||
set { lock (_lock) SetProperty(_spineObject.DebugClippings, value, v => _spineObject.DebugClippings = v); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Update(float delta)
|
||||
{
|
||||
lock (_lock) _spineObject.Update(delta);
|
||||
@@ -493,41 +497,57 @@ namespace SpineViewer.Models
|
||||
return config;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
set => ApplyObjectConfig(value);
|
||||
}
|
||||
|
||||
public void ApplyObjectConfig(SpineObjectConfigModel m, SpineObjectConfigApplyFlag flag = SpineObjectConfigApplyFlag.All)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_spineObject.Skeleton.ScaleX = value.Scale;
|
||||
_spineObject.Skeleton.ScaleY = value.Scale;
|
||||
if (flag == SpineObjectConfigApplyFlag.All)
|
||||
{
|
||||
_spineObject.Skeleton.ScaleX = m.Scale;
|
||||
_spineObject.Skeleton.ScaleY = m.Scale;
|
||||
OnPropertyChanged(nameof(Scale));
|
||||
SetProperty(_spineObject.Skeleton.ScaleX < 0, value.FlipX, v => _spineObject.Skeleton.ScaleX *= -1, nameof(FlipX));
|
||||
SetProperty(_spineObject.Skeleton.ScaleY < 0, value.FlipY, v => _spineObject.Skeleton.ScaleY *= -1, nameof(FlipY));
|
||||
SetProperty(_spineObject.Skeleton.X, value.X, v => _spineObject.Skeleton.X = v, nameof(X));
|
||||
SetProperty(_spineObject.Skeleton.Y, value.Y, v => _spineObject.Skeleton.Y = v, nameof(Y));
|
||||
SetProperty(_spineObject.UsePma, value.UsePma, v => _spineObject.UsePma = v, nameof(UsePma));
|
||||
SetProperty(_spineObject.Physics, Enum.Parse<ISkeleton.Physics>(value.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics));
|
||||
SetProperty(_spineObject.AnimationState.TimeScale, value.TimeScale, v => _spineObject.AnimationState.TimeScale = v, nameof(TimeScale));
|
||||
SetProperty(_spineObject.Skeleton.ScaleX < 0, m.FlipX, v => _spineObject.Skeleton.ScaleX *= -1, nameof(FlipX));
|
||||
SetProperty(_spineObject.Skeleton.ScaleY < 0, m.FlipY, v => _spineObject.Skeleton.ScaleY *= -1, nameof(FlipY));
|
||||
SetProperty(_spineObject.Skeleton.X, m.X, v => _spineObject.Skeleton.X = v, nameof(X));
|
||||
SetProperty(_spineObject.Skeleton.Y, m.Y, v => _spineObject.Skeleton.Y = v, nameof(Y));
|
||||
SetProperty(_spineObject.UsePma, m.UsePma, v => _spineObject.UsePma = v, nameof(UsePma));
|
||||
SetProperty(_spineObject.Physics, Enum.Parse<ISkeleton.Physics>(m.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics));
|
||||
SetProperty(_spineObject.AnimationState.TimeScale, m.TimeScale, v => _spineObject.AnimationState.TimeScale = v, nameof(TimeScale));
|
||||
}
|
||||
|
||||
foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(value.LoadedSkins))
|
||||
if (flag == SpineObjectConfigApplyFlag.All || flag == SpineObjectConfigApplyFlag.Skin)
|
||||
{
|
||||
foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(m.LoadedSkins))
|
||||
if (_spineObject.SetSkinStatus(name, false))
|
||||
SkinStatusChanged?.Invoke(this, new(name, false));
|
||||
foreach (var name in value.LoadedSkins)
|
||||
foreach (var name in m.LoadedSkins)
|
||||
if (_spineObject.SetSkinStatus(name, true))
|
||||
SkinStatusChanged?.Invoke(this, new(name, true));
|
||||
}
|
||||
|
||||
foreach (var (slotName, attachmentName) in value.SlotAttachment)
|
||||
if (flag == SpineObjectConfigApplyFlag.SlotAttachement)
|
||||
{
|
||||
foreach (var (slotName, attachmentName) in m.SlotAttachment)
|
||||
if (_spineObject.SetAttachment(slotName, attachmentName))
|
||||
SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName));
|
||||
}
|
||||
|
||||
foreach (var slotName in value.DisabledSlots)
|
||||
if (flag == SpineObjectConfigApplyFlag.SlotVisibility)
|
||||
{
|
||||
foreach (var slotName in m.DisabledSlots)
|
||||
if (_spineObject.SetSlotVisible(slotName, false))
|
||||
SlotVisibleChanged?.Invoke(this, new(slotName, false));
|
||||
}
|
||||
|
||||
if (flag == SpineObjectConfigApplyFlag.All)
|
||||
{
|
||||
// XXX: 处理空动画
|
||||
_spineObject.AnimationState.ClearTracks();
|
||||
int trackIndex = 0;
|
||||
foreach (var trConfig in value.Animations)
|
||||
foreach (var trConfig in m.Animations)
|
||||
{
|
||||
if (trConfig is not null && !string.IsNullOrEmpty(trConfig.AnimationName))
|
||||
{
|
||||
@@ -545,16 +565,16 @@ namespace SpineViewer.Models
|
||||
_spineObject.Skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
SetProperty(_spineObject.DebugTexture, value.DebugTexture, v => _spineObject.DebugTexture = v, nameof(DebugTexture));
|
||||
SetProperty(_spineObject.DebugBounds, value.DebugBounds, v => _spineObject.DebugBounds = v, nameof(DebugBounds));
|
||||
SetProperty(_spineObject.DebugBones, value.DebugBones, v => _spineObject.DebugBones = v, nameof(DebugBones));
|
||||
SetProperty(_spineObject.DebugRegions, value.DebugRegions, v => _spineObject.DebugRegions = v, nameof(DebugRegions));
|
||||
SetProperty(_spineObject.DebugMeshHulls, value.DebugMeshHulls, v => _spineObject.DebugMeshHulls = v, nameof(DebugMeshHulls));
|
||||
SetProperty(_spineObject.DebugMeshes, value.DebugMeshes, v => _spineObject.DebugMeshes = v, nameof(DebugMeshes));
|
||||
SetProperty(_spineObject.DebugBoundingBoxes, value.DebugBoundingBoxes, v => _spineObject.DebugBoundingBoxes = v, nameof(DebugBoundingBoxes));
|
||||
SetProperty(_spineObject.DebugPaths, value.DebugPaths, v => _spineObject.DebugPaths = v, nameof(DebugPaths));
|
||||
SetProperty(_spineObject.DebugPoints, value.DebugPoints, v => _spineObject.DebugPoints = v, nameof(DebugPoints));
|
||||
SetProperty(_spineObject.DebugClippings, value.DebugClippings, v => _spineObject.DebugClippings = v, nameof(DebugClippings));
|
||||
SetProperty(_spineObject.DebugTexture, m.DebugTexture, v => _spineObject.DebugTexture = v, nameof(DebugTexture));
|
||||
SetProperty(_spineObject.DebugBounds, m.DebugBounds, v => _spineObject.DebugBounds = v, nameof(DebugBounds));
|
||||
SetProperty(_spineObject.DebugBones, m.DebugBones, v => _spineObject.DebugBones = v, nameof(DebugBones));
|
||||
SetProperty(_spineObject.DebugRegions, m.DebugRegions, v => _spineObject.DebugRegions = v, nameof(DebugRegions));
|
||||
SetProperty(_spineObject.DebugMeshHulls, m.DebugMeshHulls, v => _spineObject.DebugMeshHulls = v, nameof(DebugMeshHulls));
|
||||
SetProperty(_spineObject.DebugMeshes, m.DebugMeshes, v => _spineObject.DebugMeshes = v, nameof(DebugMeshes));
|
||||
SetProperty(_spineObject.DebugBoundingBoxes, m.DebugBoundingBoxes, v => _spineObject.DebugBoundingBoxes = v, nameof(DebugBoundingBoxes));
|
||||
SetProperty(_spineObject.DebugPaths, m.DebugPaths, v => _spineObject.DebugPaths = v, nameof(DebugPaths));
|
||||
SetProperty(_spineObject.DebugPoints, m.DebugPoints, v => _spineObject.DebugPoints = v, nameof(DebugPoints));
|
||||
SetProperty(_spineObject.DebugClippings, m.DebugClippings, v => _spineObject.DebugClippings = v, nameof(DebugClippings));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,6 +633,17 @@ namespace SpineViewer.Models
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可选的应用部分模型参数项
|
||||
/// </summary>
|
||||
public enum SpineObjectConfigApplyFlag
|
||||
{
|
||||
All,
|
||||
Skin,
|
||||
SlotAttachement,
|
||||
SlotVisibility,
|
||||
}
|
||||
|
||||
public class SkinStatusChangedEventArgs(string name, bool status) : EventArgs
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
|
||||
@@ -45,7 +45,11 @@
|
||||
<s:String x:Key="Str_Reload">Reload</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">Move Up</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">Move Down</s:String>
|
||||
<s:String x:Key="Str_SpineObjectConfig">Model Config</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectConfig">Copy Config</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSkinConfig">Copy Parameters (Skin Only)</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSlotAttachmentConfig">Copy Parameters (Slot Attachments Only)</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSlotVisibilityConfig">Copy Parameters (Slot Visibility Only)</s:String>
|
||||
<s:String x:Key="Str_ApplySpineObjectConfig">Apply Config</s:String>
|
||||
<s:String x:Key="Str_SaveSpineObjectConfigToFile">Save Config to File...</s:String>
|
||||
<s:String x:Key="Str_ApplySpineObjectConfigFromFile">Apply Config from File...</s:String>
|
||||
|
||||
@@ -45,7 +45,11 @@
|
||||
<s:String x:Key="Str_Reload">再読み込み</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">上へ移動</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">下へ移動</s:String>
|
||||
<s:String x:Key="Str_SpineObjectConfig">モデル設定</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectConfig">パラメーターをコピー</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSkinConfig">パラメーターをコピー(スキンのみ)</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSlotAttachmentConfig">パラメーターをコピー(スロットのアタッチメントのみ)</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSlotVisibilityConfig">パラメーターをコピー(スロットの表示状態のみ)</s:String>
|
||||
<s:String x:Key="Str_ApplySpineObjectConfig">パラメーターを適用</s:String>
|
||||
<s:String x:Key="Str_SaveSpineObjectConfigToFile">パラメータファイルを保存...</s:String>
|
||||
<s:String x:Key="Str_ApplySpineObjectConfigFromFile">パラメータファイルを適用...</s:String>
|
||||
|
||||
@@ -45,7 +45,11 @@
|
||||
<s:String x:Key="Str_Reload">重新加载</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">上移</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">下移</s:String>
|
||||
<s:String x:Key="Str_SpineObjectConfig">模型参数</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectConfig">复制参数</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSkinConfig">复制参数(仅皮肤)</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSlotAttachmentConfig">复制参数(仅插槽附件)</s:String>
|
||||
<s:String x:Key="Str_CopySpineObjectSlotVisibilityConfig">复制参数(仅插槽可见性)</s:String>
|
||||
<s:String x:Key="Str_ApplySpineObjectConfig">应用参数</s:String>
|
||||
<s:String x:Key="Str_SaveSpineObjectConfigToFile">保存参数文件...</s:String>
|
||||
<s:String x:Key="Str_ApplySpineObjectConfigFromFile">应用参数文件...</s:String>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<Style x:Key="MyListBoxBaseStyle" TargetType="{x:Type ListBox}" BasedOn="{StaticResource ListBoxBaseStyle}">
|
||||
<Setter Property="SelectionMode" Value="Extended"/>
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>
|
||||
<!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>-->
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Visible"/>
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
<Style x:Key="MyListViewBaseStyle" TargetType="{x:Type ListView}" BasedOn="{StaticResource ListViewBaseStyle}">
|
||||
<Setter Property="SelectionMode" Value="Extended"/>
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>
|
||||
<!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>-->
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource ListViewItemBaseStyle.Small}"/>
|
||||
</Style>
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace SpineViewer.Utils
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
IndentSize = 4,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
@@ -47,7 +48,6 @@ namespace SpineViewer.Utils
|
||||
if (!quietForNotExist)
|
||||
{
|
||||
_logger.Error("Json file {0} not found", path);
|
||||
MessagePopupService.Error($"Json file {path} not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -62,13 +62,11 @@ namespace SpineViewer.Utils
|
||||
return true;
|
||||
}
|
||||
_logger.Error("Null data in file {0}", path);
|
||||
MessagePopupService.Error($"Null data in file {path}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to read json file {0}, {1}", path, ex.Message);
|
||||
MessagePopupService.Error($"Failed to read json file {path}, {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
obj = default;
|
||||
@@ -90,11 +88,24 @@ namespace SpineViewer.Utils
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to save json file {0}, {1}", path, ex.Message);
|
||||
MessagePopupService.Error($"Failed to save json file {path}, {ex.ToString()}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string Serialize<T>(T obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Serialize(obj, _jsonOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to serialize json object {0}", ex.Message);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorJsonConverter : JsonConverter<Color>
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
/// 临时对象, 存储复制的模型参数
|
||||
/// </summary>
|
||||
private SpineObjectConfigModel? _copiedSpineObjectConfigModel = null;
|
||||
private SpineObjectConfigApplyFlag _copiedConfigFlag = SpineObjectConfigApplyFlag.All;
|
||||
|
||||
public SpineObjectListViewModel(MainWindowViewModel mainViewModel)
|
||||
{
|
||||
@@ -99,6 +100,127 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从路径列表添加对象
|
||||
/// </summary>
|
||||
/// <param name="paths">可以是文件和文件夹</param>
|
||||
public void AddSpineObjectFromFileList(IEnumerable<string> paths)
|
||||
{
|
||||
List<string> validPaths = [];
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var lowerPath = path.ToLowerInvariant();
|
||||
if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith))
|
||||
validPaths.Add(path);
|
||||
}
|
||||
else if (Directory.Exists(path))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
var lowerPath = file.ToLowerInvariant();
|
||||
if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith))
|
||||
validPaths.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validPaths.Count > 1)
|
||||
{
|
||||
if (validPaths.Count > 100)
|
||||
{
|
||||
if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_TooManyItemsToAddQuest, validPaths.Count)))
|
||||
return;
|
||||
}
|
||||
ProgressService.RunAsync((pr, ct) => AddSpineObjectsTask(
|
||||
validPaths.ToArray(), pr, ct),
|
||||
AppResource.Str_AddSpineObjectsTitle
|
||||
);
|
||||
}
|
||||
else if (validPaths.Count > 0)
|
||||
{
|
||||
InsertSpineObject(validPaths[0]);
|
||||
_logger.LogCurrentProcessMemoryUsage();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于后台添加模型的任务方法
|
||||
/// </summary>
|
||||
private void AddSpineObjectsTask(string[] paths, IProgressReporter reporter, CancellationToken ct)
|
||||
{
|
||||
int totalCount = paths.Length;
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
|
||||
_vmMain.ProgressState = TaskbarItemProgressState.Normal;
|
||||
_vmMain.ProgressValue = 0;
|
||||
|
||||
reporter.Total = totalCount;
|
||||
reporter.Done = 0;
|
||||
reporter.ProgressText = $"[0/{totalCount}]";
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var skelPath = paths[i];
|
||||
reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";
|
||||
|
||||
if (InsertSpineObject(skelPath))
|
||||
success++;
|
||||
else
|
||||
error++;
|
||||
|
||||
reporter.Done = i + 1;
|
||||
reporter.ProgressText = $"[{i + 1}/{totalCount}] {skelPath}";
|
||||
_vmMain.ProgressValue = (i + 1f) / totalCount;
|
||||
}
|
||||
_vmMain.ProgressState = TaskbarItemProgressState.None;
|
||||
|
||||
if (error > 0)
|
||||
_logger.Warn("Batch load {0} successfully, {1} failed", success, error);
|
||||
else
|
||||
_logger.Info("{0} skel loaded successfully", success);
|
||||
|
||||
_logger.LogCurrentProcessMemoryUsage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全地在列表头添加一个模型, 发生错误会输出日志
|
||||
/// </summary>
|
||||
/// <returns>是否添加成功</returns>
|
||||
private bool InsertSpineObject(string skelPath, string? atlasPath = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sp = new SpineObjectModel(skelPath, atlasPath);
|
||||
lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
|
||||
if (Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region 模型列表管理菜单项实现
|
||||
|
||||
/// <summary>
|
||||
/// 弹窗添加单模型命令
|
||||
/// </summary>
|
||||
@@ -349,18 +471,53 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 模型参数管理菜单项实现
|
||||
|
||||
/// <summary>
|
||||
/// 复制模型参数
|
||||
/// </summary>
|
||||
public RelayCommand<IList?> Cmd_CopySpineObjectConfig => _cmd_CopySpineObjectConfig ??= new(CopySpineObjectConfig_Execute, CopySpineObjectConfig_CanExecute);
|
||||
public RelayCommand<IList?> Cmd_CopySpineObjectConfig => _cmd_CopySpineObjectConfig ??= new(
|
||||
args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.All),
|
||||
CopySpineObjectConfig_CanExecute
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_CopySpineObjectConfig;
|
||||
|
||||
private void CopySpineObjectConfig_Execute(IList? args)
|
||||
/// <summary>
|
||||
/// 复制模型参数 (仅皮肤)
|
||||
/// </summary>
|
||||
public RelayCommand<IList?> Cmd_CopySpineObjectSkinConfig => _cmd_CopySpineObjectSkinConfig ??= new(
|
||||
args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.Skin),
|
||||
CopySpineObjectConfig_CanExecute
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_CopySpineObjectSkinConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 复制模型参数 (仅插槽附件)
|
||||
/// </summary>
|
||||
public RelayCommand<IList?> Cmd_CopySpineObjectSlotAttachmentConfig => _cmd_CopySpineObjectSlotAttachmentConfig ??= new(
|
||||
args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.SlotAttachement),
|
||||
CopySpineObjectConfig_CanExecute
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_CopySpineObjectSlotAttachmentConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 复制模型参数 (仅插槽可见性)
|
||||
/// </summary>
|
||||
public RelayCommand<IList?> Cmd_CopySpineObjectSlotVisibilityConfig => _cmd_CopySpineObjectSlotVisibilityConfig ??= new(
|
||||
args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.SlotVisibility),
|
||||
CopySpineObjectConfig_CanExecute
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_CopySpineObjectSlotVisibilityConfig;
|
||||
|
||||
private void CopySpineObjectConfig_Execute(IList? args, SpineObjectConfigApplyFlag flag)
|
||||
{
|
||||
if (!CopySpineObjectConfig_CanExecute(args)) return;
|
||||
var sp = (SpineObjectModel)args[0];
|
||||
_copiedSpineObjectConfigModel = sp.ObjectConfig;
|
||||
_logger.Info("Copy config from model: {0}", sp.Name);
|
||||
_copiedConfigFlag = flag;
|
||||
_logger.Info("Copy config[{0}] from model: {1}", flag, sp.Name);
|
||||
}
|
||||
|
||||
private bool CopySpineObjectConfig_CanExecute(IList? args)
|
||||
@@ -381,8 +538,8 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
if (!ApplySpineObjectConfig_CanExecute(args)) return;
|
||||
foreach (SpineObjectModel sp in args)
|
||||
{
|
||||
sp.ObjectConfig = _copiedSpineObjectConfigModel;
|
||||
_logger.Info("Apply config to model: {0}", sp.Name);
|
||||
sp.ApplyObjectConfig(_copiedSpineObjectConfigModel, _copiedConfigFlag);
|
||||
_logger.Info("Apply config[{0}] to model: {1}", _copiedConfigFlag, sp.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,124 +596,9 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从路径列表添加对象
|
||||
/// </summary>
|
||||
/// <param name="paths">可以是文件和文件夹</param>
|
||||
public void AddSpineObjectFromFileList(IEnumerable<string> paths)
|
||||
{
|
||||
List<string> validPaths = [];
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var lowerPath = path.ToLowerInvariant();
|
||||
if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith))
|
||||
validPaths.Add(path);
|
||||
}
|
||||
else if (Directory.Exists(path))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
var lowerPath = file.ToLowerInvariant();
|
||||
if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith))
|
||||
validPaths.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
if (validPaths.Count > 1)
|
||||
{
|
||||
if (validPaths.Count > 100)
|
||||
{
|
||||
if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_TooManyItemsToAddQuest, validPaths.Count)))
|
||||
return;
|
||||
}
|
||||
ProgressService.RunAsync((pr, ct) => AddSpineObjectsTask(
|
||||
validPaths.ToArray(), pr, ct),
|
||||
AppResource.Str_AddSpineObjectsTitle
|
||||
);
|
||||
}
|
||||
else if (validPaths.Count > 0)
|
||||
{
|
||||
InsertSpineObject(validPaths[0]);
|
||||
_logger.LogCurrentProcessMemoryUsage();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于后台添加模型的任务方法
|
||||
/// </summary>
|
||||
private void AddSpineObjectsTask(string[] paths, IProgressReporter reporter, CancellationToken ct)
|
||||
{
|
||||
int totalCount = paths.Length;
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
|
||||
_vmMain.ProgressState = TaskbarItemProgressState.Normal;
|
||||
_vmMain.ProgressValue = 0;
|
||||
|
||||
reporter.Total = totalCount;
|
||||
reporter.Done = 0;
|
||||
reporter.ProgressText = $"[0/{totalCount}]";
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var skelPath = paths[i];
|
||||
reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";
|
||||
|
||||
if (InsertSpineObject(skelPath))
|
||||
success++;
|
||||
else
|
||||
error++;
|
||||
|
||||
reporter.Done = i + 1;
|
||||
reporter.ProgressText = $"[{i + 1}/{totalCount}] {skelPath}";
|
||||
_vmMain.ProgressValue = (i + 1f) / totalCount;
|
||||
}
|
||||
_vmMain.ProgressState = TaskbarItemProgressState.None;
|
||||
|
||||
if (error > 0)
|
||||
_logger.Warn("Batch load {0} successfully, {1} failed", success, error);
|
||||
else
|
||||
_logger.Info("{0} skel loaded successfully", success);
|
||||
|
||||
_logger.LogCurrentProcessMemoryUsage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全地在列表头添加一个模型, 发生错误会输出日志
|
||||
/// </summary>
|
||||
/// <returns>是否添加成功</returns>
|
||||
private bool InsertSpineObject(string skelPath, string? atlasPath = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sp = new SpineObjectModel(skelPath, atlasPath);
|
||||
lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
|
||||
if (Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#region 工作区参数实现
|
||||
|
||||
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects
|
||||
{
|
||||
@@ -681,5 +723,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,21 +170,32 @@
|
||||
Command="{Binding Cmd_MoveDownSpineObject}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{DynamicResource Str_SpineObjectConfig}">
|
||||
<MenuItem Header="{DynamicResource Str_CopySpineObjectConfig}"
|
||||
InputGestureText="Ctrl+Shift+C"
|
||||
Command="{Binding Cmd_CopySpineObjectConfig}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_CopySpineObjectSkinConfig}"
|
||||
Command="{Binding Cmd_CopySpineObjectSkinConfig}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_CopySpineObjectSlotAttachmentConfig}"
|
||||
Command="{Binding Cmd_CopySpineObjectSlotAttachmentConfig}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_CopySpineObjectSlotVisibilityConfig}"
|
||||
Command="{Binding Cmd_CopySpineObjectSlotVisibilityConfig}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_ApplySpineObjectConfig}"
|
||||
InputGestureText="Ctrl+Shift+V"
|
||||
Command="{Binding Cmd_ApplySpineObjectConfig}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{DynamicResource Str_ApplySpineObjectConfigFromFile}"
|
||||
Command="{Binding Cmd_ApplySpineObjectConfigFromFile}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_SaveSpineObjectConfigToFile}"
|
||||
Command="{Binding Cmd_SaveSpineObjectConfigToFile}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<Separator/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="{DynamicResource Str_Export}">
|
||||
<MenuItem Header="{DynamicResource Str_ExportFrame}"
|
||||
Command="{Binding FrameExporterViewModel.Cmd_Export}"
|
||||
|
||||
Reference in New Issue
Block a user