Compare commits

...

34 Commits

Author SHA1 Message Date
ww-rm
2fecf4223a 更新至v0.12.17 2025-06-14 20:08:54 +08:00
ww-rm
2953ec44fb update changelog 2025-06-14 20:08:45 +08:00
ww-rm
77399b4524 改善动画边界检测 2025-06-14 20:07:09 +08:00
ww-rm
c0e2bb81e5 更新至v0.12.16 2025-06-11 13:41:25 +08:00
ww-rm
55ebcc1857 合并导出时也允许自动时长 2025-06-11 13:38:55 +08:00
ww-rm
292ede8461 修复工作流错误 2025-05-28 18:22:33 +08:00
ww-rm
63ed8d7ca4 修复工作流错误 2025-05-28 17:27:55 +08:00
ww-rm
ce7c6f3802 修复工作流错误 2025-05-28 17:14:08 +08:00
ww-rm
0b478cab18 修复工作流错误 2025-05-28 17:09:58 +08:00
ww-rm
b7f5f24e6f Merge branch 'dev/wf' of github.com:ww-rm/SpineViewer into dev/wf 2025-05-28 16:59:51 +08:00
ww-rm
150331d2e4 修复工作流错误 2025-05-28 16:59:38 +08:00
ww-rm
4d1aec9ed8 Merge branch 'release/wf' into dev/wf 2025-05-28 16:45:42 +08:00
ww-rm
0cb325820b 修复工作流版本提取错误 2025-05-28 16:38:00 +08:00
ww-rm
2a862b28be 更改工作流自动获取版本号 2025-05-28 16:29:29 +08:00
ww-rm
c2935f49e9 更新至v0.12.15 2025-05-28 16:29:29 +08:00
ww-rm
22043f8f38 update changelog 2025-05-28 16:29:29 +08:00
ww-rm
3020a818f0 修复附件类型枚举量的字符串大小写问题 2025-05-28 16:29:29 +08:00
ww-rm
30177e8d7f 更改工作流自动获取版本号 2025-05-28 16:28:12 +08:00
ww-rm
3ad49838be 更新至v0.12.15 2025-05-28 16:05:55 +08:00
ww-rm
3e480abd44 update changelog 2025-05-28 16:05:42 +08:00
ww-rm
49f6b28aef 修复附件类型枚举量的字符串大小写问题 2025-05-28 16:05:09 +08:00
ww-rm
b81d13b582 更新至v0.12.14 2025-05-28 09:13:03 +08:00
ww-rm
04eb3cb640 update changelog 2025-05-28 09:12:37 +08:00
ww-rm
0ac75a088a 修复curve读取错误 2025-05-28 09:11:25 +08:00
ww-rm
cd652a72a1 更新至v0.12.13 2025-05-19 10:31:34 +08:00
ww-rm
828ff30dbf update changelog 2025-05-19 10:31:15 +08:00
ww-rm
f452fe8a71 生成文件增加额外的随机后缀 2025-05-19 10:30:29 +08:00
ww-rm
15e29a3b8a 修复readattachmentline里的顺序错误 2025-05-17 10:47:49 +08:00
ww-rm
5c6e98f5e1 更新至v0.12.12 2025-05-13 14:21:13 +08:00
ww-rm
ef06073119 update changelog 2025-05-13 14:19:52 +08:00
ww-rm
bca8b0ad85 补充SkinnedMeshAttachment附件的渲染 2025-05-13 14:19:15 +08:00
ww-rm
4983b1fa88 更新至v0.12.11 2025-05-08 18:35:23 +08:00
ww-rm
f6b6d9f0e7 update changelog 2025-05-08 18:35:12 +08:00
ww-rm
12a168df92 修复atlas null 引用导致的闪退 2025-05-08 18:34:43 +08:00
17 changed files with 239 additions and 76 deletions

View File

@@ -1,46 +1,80 @@
name: Build & Release
on:
push:
tags:
- 'v*.*.*'
pull_request:
branches:
- release/wf
types:
- closed
jobs:
build-release:
if: ${{ github.event.pull_request.merged == true }}
runs-on: windows-latest
env:
PROJECT_NAME: SpineViewer
VERSION: ${{ github.ref_name }}
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-tags: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
dotnet-version: "8.0.x"
- name: Extract version from csproj
shell: pwsh
run: |
[xml]$proj = Get-Content "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj"
$VERSION_NUM = $proj.Project.PropertyGroup.Version
$VERSION_TAG = "v$VERSION_NUM"
"VERSION=$VERSION_TAG" >> $env:GITHUB_ENV
- name: Check Version Tag
shell: pwsh
run: |
if (-not $env:VERSION) {
Write-Error "Version tag not found in csproj file."
exit 1
}
Write-Host "Version tag found: $env:VERSION"
- name: Tag merge commit
shell: pwsh
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag $env:VERSION
git push --tags
- name: Publish FrameworkDependent version
shell: pwsh
run: |
dotnet publish ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj -c Release -r win-x64 --sc false -p:PublishSingleFile=true -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_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 -p:PublishSingleFile=true -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
- name: Create release directory
run: mkdir release
shell: pwsh
run: |
New-Item -ItemType Directory -Path release -Force | Out-Null
- name: Compress FrameworkDependent version
shell: pwsh
run: |
Compress-Archive -Path "publish/${env:PROJECT_NAME}-${env:VERSION}" -DestinationPath "release/${env:PROJECT_NAME}-${env:VERSION}.zip" -Force
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION.zip" -Force
- name: Compress SelfContained version
shell: pwsh
run: |
Compress-Archive -Path "publish/${env:PROJECT_NAME}-${env:VERSION}-SelfContained" -DestinationPath "release/${env:PROJECT_NAME}-${env:VERSION}-SelfContained.zip" -Force
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION-SelfContained.zip" -Force
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
@@ -51,7 +85,7 @@ jobs:
release_name: Release ${{ env.VERSION }}
draft: false
prerelease: false
- name: Upload FrameworkDependent zip
uses: actions/upload-release-asset@v1
env:
@@ -61,7 +95,7 @@ jobs:
asset_path: release/${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
asset_name: ${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
asset_content_type: application/zip
- name: Upload SelfContained zip
uses: actions/upload-release-asset@v1
env:

View File

@@ -1,5 +1,33 @@
# CHANGELOG
## v0.12.17
- 动画边界检测的帧率提高到100帧每秒
## v0.12.16
- 导出多个时允许自动时长
## v0.12.15
- 修复附件类型枚举量字符串大小写问题
## v0.12.14
- 修复 v38 文件读取的小 bug
## v0.12.13
- 导出文件名增加额外的随机字符串
## v0.12.12
- 修复 2.1 版本遗漏的 SkinnedMeshAttachment 附件渲染
## v0.12.11
- 修复可能的闪退错误
## v0.12.10
- 增加纹理全局加载选项

View File

@@ -51,6 +51,17 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
[RotateMode.ChainScale] = "chainScale",
};
private static readonly Dictionary<AttachmentType, string> AttachmentTypeJsonValue = new()
{
[AttachmentType.Region] = "region",
[AttachmentType.Boundingbox] = "bounding",
[AttachmentType.Mesh] = "mesh",
[AttachmentType.Linkedmesh] = "linkedmesh",
[AttachmentType.Path] = "path",
[AttachmentType.Point] = "point",
[AttachmentType.Clipping] = "clipping",
};
private BinaryReader reader = null;
private JsonObject root = null;
private bool nonessential = false;
@@ -298,7 +309,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
var name = reader.ReadStringRef() ?? keyName;
var type = (AttachmentType)reader.ReadByte();
attachment["name"] = name;
attachment["type"] = type.ToString();
attachment["type"] = AttachmentTypeJsonValue[type];
switch (type)
{
case AttachmentType.Region:
@@ -586,7 +597,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
["compress"] = reader.ReadBoolean(),
["stretch"] = reader.ReadBoolean(),
};
if (frameCount > 1) ReadCurve(o);
if (frameIdx < frameCount - 1) ReadCurve(o);
frames.Add(o);
}
}
@@ -613,7 +624,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
["scaleMix"] = reader.ReadFloat(),
["shearMix"] = reader.ReadFloat(),
};
if (frameCount > 1) ReadCurve(o);
if (frameIdx < frameCount - 1) ReadCurve(o);
frames.Add(o);
}
}

View File

@@ -52,9 +52,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
private static readonly Dictionary<AttachmentType, string> AttachmentTypeJsonValue = new()
{
[AttachmentType.Region] = "region",
[AttachmentType.Boundingbox] = "boundingBox",
[AttachmentType.Boundingbox] = "boundingbox",
[AttachmentType.Mesh] = "mesh",
[AttachmentType.Linkedmesh] = "linkedMesh",
[AttachmentType.Linkedmesh] = "linkedmesh",
[AttachmentType.Path] = "path",
[AttachmentType.Point] = "point",
[AttachmentType.Clipping] = "clipping",

View File

@@ -52,7 +52,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
[AttachmentType.Region] = "region",
[AttachmentType.Boundingbox] = "bounding",
[AttachmentType.Mesh] = "mesh",
[AttachmentType.Linkedmesh] = "linkedMesh",
[AttachmentType.Linkedmesh] = "linkedmesh",
[AttachmentType.Path] = "path",
[AttachmentType.Point] = "point",
[AttachmentType.Clipping] = "clipping",
@@ -1069,14 +1069,14 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
{
["time"] = reader.ReadFloat(),
};
ReadCurve(frame, 1);
frame = o;
end = reader.ReadVarInt();
if (end > 0)
{
frame["offset"] = reader.ReadVarInt();
frame["vertices"] = ReadFloatArray(end);
}
ReadCurve(frame, 1);
frame = o;
frames.Add(frame);
}
break;

View File

@@ -50,7 +50,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
public SpineObject21(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try { atlas = new Atlas(AtlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
// 先尝试二进制文件
@@ -70,6 +71,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
catch
{
// 都不行就报错
atlas.Dispose();
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
}
}
@@ -96,7 +98,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
atlas?.Dispose();
}
public override string FileVersion { get => skeletonData.Version; }
@@ -220,10 +222,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
tmpSkeleton.Update(0);
tmpSkeleton.UpdateWorldTransform();
// 按 10 帧每秒计算边框
// 按 100 帧每秒计算边框
var bounds = getCurrentBounds();
float[] _ = [];
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
{
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h);
bounds = bounds.Union(new(x, y, w, h));
@@ -258,7 +260,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
SFML.Graphics.Texture texture;
float[] worldVertices = worldVerticesBuffer; // 顶点世界坐标, 连续的 [x0, y0, x1, y1, ...] 坐标值
int worldVerticesCount; // 等于顶点数组的长度除以 2
//int worldVerticesCount; // 等于顶点数组的长度除以 2
int[] worldTriangleIndices; // 三角形索引, 从顶点坐标数组取的时候要乘以 2, 最大值是 worldVerticesCount - 1
int worldTriangleIndicesLength; // 三角形索引数组长度
float[] uvs; // 纹理坐标
@@ -272,7 +274,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
texture = (SFML.Graphics.Texture)((AtlasRegion)regionAttachment.RendererObject).page.rendererObject;
regionAttachment.ComputeWorldVertices(slot.Bone, worldVertices);
worldVerticesCount = 4;
//worldVerticesCount = 4;
worldTriangleIndices = [0, 1, 2, 2, 3, 0];
worldTriangleIndicesLength = 6;
uvs = regionAttachment.UVs;
@@ -288,7 +290,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
if (meshAttachment.Vertices.Length > worldVertices.Length)
worldVertices = worldVerticesBuffer = new float[meshAttachment.Vertices.Length * 2];
meshAttachment.ComputeWorldVertices(slot, worldVertices);
worldVerticesCount = meshAttachment.Vertices.Length / 2;
//worldVerticesCount = meshAttachment.Vertices.Length / 2;
worldTriangleIndices = meshAttachment.Triangles;
worldTriangleIndicesLength = meshAttachment.Triangles.Length;
uvs = meshAttachment.UVs;
@@ -297,6 +299,22 @@ namespace SpineViewer.Spine.Implementations.SpineObject
tintB *= meshAttachment.B;
tintA *= meshAttachment.A;
}
else if (attachment is SkinnedMeshAttachment skinnedMeshAttachment)
{
texture = (SFML.Graphics.Texture)((AtlasRegion)skinnedMeshAttachment.RendererObject).page.rendererObject;
if (skinnedMeshAttachment.UVs.Length > worldVertices.Length)
worldVertices = worldVerticesBuffer = new float[skinnedMeshAttachment.UVs.Length * 2];
skinnedMeshAttachment.ComputeWorldVertices(slot, worldVertices);
//worldVerticesCount = skinnedMeshAttachment.Vertices.Length / 2;
worldTriangleIndices = skinnedMeshAttachment.Triangles;
worldTriangleIndicesLength = skinnedMeshAttachment.Triangles.Length;
uvs = skinnedMeshAttachment.UVs;
tintR *= skinnedMeshAttachment.R;
tintG *= skinnedMeshAttachment.G;
tintB *= skinnedMeshAttachment.B;
tintA *= skinnedMeshAttachment.A;
}
// 2.1.x 不支持剪裁
//else if (attachment is ClippingAttachment clippingAttachment)
//{
@@ -430,6 +448,37 @@ namespace SpineViewer.Spine.Implementations.SpineObject
vt.Position.Y = worldVerticesBuffer[idx2 + 1];
lineVertices.Append(vt); lineVertices.Append(vt);
vt.Position.X = worldVerticesBuffer[idx0];
vt.Position.Y = worldVerticesBuffer[idx0 + 1];
lineVertices.Append(vt);
}
}
else if (slot.Attachment is SkinnedMeshAttachment skinnedMeshAttachment)
{
if (skinnedMeshAttachment.UVs.Length > worldVerticesBuffer.Length)
worldVerticesBuffer = new float[skinnedMeshAttachment.UVs.Length * 2];
skinnedMeshAttachment.ComputeWorldVertices(slot, worldVerticesBuffer);
var triangleIndices = skinnedMeshAttachment.Triangles;
for (int i = 0; i < triangleIndices.Length; i += 3)
{
var idx0 = triangleIndices[i] * 2;
var idx1 = triangleIndices[i + 1] * 2;
var idx2 = triangleIndices[i + 2] * 2;
vt.Position.X = worldVerticesBuffer[idx0];
vt.Position.Y = worldVerticesBuffer[idx0 + 1];
lineVertices.Append(vt);
vt.Position.X = worldVerticesBuffer[idx1];
vt.Position.Y = worldVerticesBuffer[idx1 + 1];
lineVertices.Append(vt); lineVertices.Append(vt);
vt.Position.X = worldVerticesBuffer[idx2];
vt.Position.Y = worldVerticesBuffer[idx2 + 1];
lineVertices.Append(vt); lineVertices.Append(vt);
vt.Position.X = worldVerticesBuffer[idx0];
vt.Position.Y = worldVerticesBuffer[idx0 + 1];
lineVertices.Append(vt);
@@ -452,6 +501,34 @@ namespace SpineViewer.Spine.Implementations.SpineObject
var hullLength = (meshAttachment.HullLength >> 1) << 1;
if (debugMeshHulls && hullLength > 2)
{
vt.Position.X = worldVerticesBuffer[0];
vt.Position.Y = worldVerticesBuffer[1];
lineVertices.Append(vt);
for (int i = 2; i < hullLength; i += 2)
{
vt.Position.X = worldVerticesBuffer[i];
vt.Position.Y = worldVerticesBuffer[i + 1];
lineVertices.Append(vt);
lineVertices.Append(vt);
}
vt.Position.X = worldVerticesBuffer[0];
vt.Position.Y = worldVerticesBuffer[1];
lineVertices.Append(vt);
}
}
else if (slot.Attachment is SkinnedMeshAttachment skinnedMeshAttachment)
{
if (skinnedMeshAttachment.UVs.Length > worldVerticesBuffer.Length)
worldVerticesBuffer = new float[skinnedMeshAttachment.UVs.Length * 2];
skinnedMeshAttachment.ComputeWorldVertices(slot, worldVerticesBuffer);
var hullLength = (skinnedMeshAttachment.HullLength >> 1) << 1;
if (debugMeshHulls && hullLength > 2)
{
vt.Position.X = worldVerticesBuffer[0];

View File

@@ -49,7 +49,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
public SpineObject36(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try { atlas = new Atlas(AtlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
// 先尝试二进制文件
@@ -69,6 +70,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
catch
{
// 都不行就报错
atlas.Dispose();
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
}
}
@@ -95,7 +97,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
atlas?.Dispose();
}
public override string FileVersion { get => skeletonData.Version; }
@@ -220,10 +222,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
tmpSkeleton.Update(0);
tmpSkeleton.UpdateWorldTransform();
// 按 10 帧每秒计算边框
// 按 100 帧每秒计算边框
var bounds = getCurrentBounds();
float[] _ = [];
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
{
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
bounds = bounds.Union(new(x, y, w, h));

View File

@@ -46,7 +46,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
public SpineObject37(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try { atlas = new Atlas(AtlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
// 先尝试二进制文件
@@ -66,6 +67,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
catch
{
// 都不行就报错
atlas.Dispose();
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
}
}
@@ -92,7 +94,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
atlas?.Dispose();
}
public override string FileVersion { get => skeletonData.Version; }
@@ -217,10 +219,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
tmpSkeleton.Update(0);
tmpSkeleton.UpdateWorldTransform();
// 按 10 帧每秒计算边框
// 按 100 帧每秒计算边框
var bounds = getCurrentBounds();
float[] _ = [];
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
{
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
bounds = bounds.Union(new(x, y, w, h));

View File

@@ -50,7 +50,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
public SpineObject38(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try { atlas = new Atlas(AtlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
// 先尝试二进制文件
@@ -70,6 +71,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
catch (Exception ex)
{
// 都不行就报错
atlas.Dispose();
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}", ex);
}
}
@@ -96,7 +98,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
atlas?.Dispose();
}
public override string FileVersion { get => skeletonData.Version; }
@@ -215,10 +217,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
tmpSkeleton.Update(0);
tmpSkeleton.UpdateWorldTransform();
// 按 10 帧每秒计算边框
// 按 100 帧每秒计算边框
var bounds = getCurrentBounds();
float[] _ = [];
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
{
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
bounds = bounds.Union(new(x, y, w, h));

View File

@@ -48,7 +48,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
public SpineObject40(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try { atlas = new Atlas(AtlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
// 先尝试二进制文件
@@ -68,6 +69,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
catch
{
// 都不行就报错
atlas.Dispose();
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
}
}
@@ -95,7 +97,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
atlas?.Dispose();
}
public override string FileVersion { get => skeletonData.Version; }
@@ -214,10 +216,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
tmpSkeleton.Update(0);
tmpSkeleton.UpdateWorldTransform();
// 按 10 帧每秒计算边框
// 按 100 帧每秒计算边框
var bounds = getCurrentBounds();
float[] _ = [];
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
{
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
bounds = bounds.Union(new(x, y, w, h));

View File

@@ -48,7 +48,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
public SpineObject41(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try { atlas = new Atlas(AtlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
// 先尝试二进制文件
@@ -68,6 +69,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
catch
{
// 都不行就报错
atlas.Dispose();
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
}
}
@@ -95,7 +97,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
atlas?.Dispose();
}
public override string FileVersion { get => skeletonData.Version; }
@@ -214,10 +216,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
//tmpSkeleton.Update(0);
tmpSkeleton.UpdateWorldTransform();
// 按 10 帧每秒计算边框
// 按 100 帧每秒计算边框
var bounds = getCurrentBounds();
float[] _ = [];
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
{
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
bounds = bounds.Union(new(x, y, w, h));

View File

@@ -48,7 +48,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
public SpineObject42(string skelPath, string atlasPath) : base(skelPath, atlasPath)
{
atlas = new Atlas(AtlasPath, textureLoader);
try { atlas = new Atlas(AtlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
// 先尝试二进制文件
@@ -68,6 +69,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
catch
{
// 都不行就报错
atlas.Dispose();
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
}
}
@@ -95,7 +97,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
atlas.Dispose();
atlas?.Dispose();
}
public override string FileVersion { get => skeletonData.Version; }
@@ -214,10 +216,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
tmpSkeleton.Update(0);
tmpSkeleton.UpdateWorldTransform(Skeleton.Physics.Update);
// 按 10 帧每秒计算边框
// 按 100 帧每秒计算边框
var bounds = getCurrentBounds();
float[] _ = [];
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
{
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
bounds = bounds.Union(new(x, y, w, h));

View File

@@ -57,7 +57,7 @@ namespace SpineViewer.Spine.SpineExporter
var noteSuffix = FileNameNoteSuffix;
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
var filename = $"ffmpeg_{timestamp}_{FPS:f0}{noteSuffix}{Suffix}";
var filename = $"ffmpeg_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{FPS:f0}{noteSuffix}{Suffix}";
// 导出单个时必定提供输出文件夹
var savePath = Path.Combine(OutputDir, filename);
@@ -86,7 +86,7 @@ namespace SpineViewer.Spine.SpineExporter
{
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
var filename = $"{spine.Name}_{timestamp}_{FPS:f0}{noteSuffix}{Suffix}";
var filename = $"{spine.Name}_{timestamp}_{spine.ID[..6]}_{FPS:f0}{noteSuffix}{Suffix}";
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
var savePath = Path.Combine(OutputDir ?? spine.AssetsDir, filename);

View File

@@ -47,7 +47,7 @@ namespace SpineViewer.Spine.SpineExporter
protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
// 导出单个时必定提供输出文件夹
var filename = $"frame_{timestamp}{ImageFormat.GetSuffix()}";
var filename = $"frame_{timestamp}_{Guid.NewGuid().ToString()[..6]}{ImageFormat.GetSuffix()}";
var savePath = Path.Combine(OutputDir, filename);
worker?.ReportProgress(0, $"{Properties.Resources.process} 0/1");
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.SpineExporter
var spine = spinesToRender[i];
// 逐个导出时如果提供了输出文件夹, 则全部导出到输出文件夹, 否则输出到各自的文件夹
var filename = $"{spine.Name}_{timestamp}{ImageFormat.GetSuffix()}";
var filename = $"{spine.Name}_{timestamp}_{spine.ID[..6]}{ImageFormat.GetSuffix()}";
var savePath = Path.Combine(OutputDir ?? spine.AssetsDir, filename);
try

View File

@@ -22,14 +22,16 @@ namespace SpineViewer.Spine.SpineExporter
protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
var uniqueSuffix = Guid.NewGuid().ToString()[..6];
// 导出单个时必定提供输出文件夹,
var saveDir = Path.Combine(OutputDir, $"frames_{timestamp}_{FPS:f0}");
var saveDir = Path.Combine(OutputDir, $"frames_{timestamp}_{uniqueSuffix}_{FPS:f0}");
Directory.CreateDirectory(saveDir);
int frameIdx = 0;
foreach (var frame in GetFrames(spinesToRender, worker))
{
var filename = $"frames_{timestamp}_{FPS:f0}_{frameIdx:d6}{Suffix}";
var filename = $"frames_{timestamp}_{uniqueSuffix}_{FPS:f0}_{frameIdx:d6}{Suffix}";
var savePath = Path.Combine(saveDir, filename);
try
@@ -56,14 +58,14 @@ namespace SpineViewer.Spine.SpineExporter
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
var subDir = $"{spine.Name}_{timestamp}_{FPS:f0}";
var subDir = $"{spine.Name}_{timestamp}_{spine.ID[..6]}_{FPS:f0}";
var saveDir = Path.Combine(OutputDir ?? spine.AssetsDir, subDir);
Directory.CreateDirectory(saveDir);
int frameIdx = 0;
foreach (var frame in GetFrames(spine, worker))
{
var filename = $"{spine.Name}_{timestamp}_{FPS:f0}_{frameIdx:d6}{Suffix}";
var filename = $"{spine.Name}_{timestamp}_{spine.ID[..6]}_{FPS:f0}_{frameIdx:d6}{Suffix}";
var savePath = Path.Combine(saveDir, filename);
try

View File

@@ -30,15 +30,6 @@ namespace SpineViewer.Spine.SpineExporter
/// </summary>
public bool KeepLast { get; set; } = true;
public override string? Validate()
{
if (base.Validate() is string error)
return error;
if (IsExportSingle && Duration < 0)
return Properties.Resources.negativeDuration;
return null;
}
/// <summary>
/// 生成单个模型的帧序列
/// </summary>
@@ -93,8 +84,16 @@ namespace SpineViewer.Spine.SpineExporter
/// </summary>
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
// 导出单个时必须根据 Duration 决定导出时长
// 导出单个时取所有模型的所有轨道时长最大值
var duration = Duration;
if (duration < 0)
{
duration = spinesToRender.Select(
sp => sp.GetTrackIndices().Select(
i => sp.GetAnimationDuration(sp.GetAnimation(i))
).DefaultIfEmpty(0).Max()
).Max();
}
float delta = 1f / FPS;
int total = (int)(duration * FPS); // 完整帧的数量

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.12.10</Version>
<Version>0.12.17</Version>
<OutputType>WinExe</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>appicon.ico</ApplicationIcon>