新增3.4版本支持

This commit is contained in:
tianmo committed 2026-09-10 13:53:43 +08:00
1 parent 4c5033d2f9
commit 9635428f78
21 files changed
+5425 -51

No files matched your search

+58 -10
View File
@@ -137,7 +137,7 @@
<span id="speedVal">1.0x</span>
</label>
<label title="用于以 Premultiplied Alpha(PMA)方式导出的贴图">
<input type="checkbox" id="pmaCheck"> 预乘
<input type="checkbox" id="pmaCheck" disabled> 预乘
</label>
<div class="spacer"></div>
<button id="thumbBtn" title="使用当前帧生成 400×400 缩略图">🖼 生成缩略图</button>
@@ -237,9 +237,11 @@
let skeleton = null, state = null, stateData = null, skeletonData = null;
let renderer = null;
let activeRuntimeDir = null;
let activeVersion = null;
let currentAnim = 0, currentSkin = null;
let playing = true, loop = true, speed = 1;
let premultipliedAlpha = false;
let previewSettings = null;
let timeKeeper = null, lastTime = 0, running = false;
let seeking = false; // 用户正在拖动时间轴
let duration = 0; // 当前动画时长
@@ -414,6 +416,13 @@
}
async function setEagleCustomThumbnail(itemId, thumbnailPath) {
if (/\.(atlas\.txt|skel\.bytes)$/i.test(filePath)) {
const response = await fetch('http://localhost:41595/api/item/info?id=' + encodeURIComponent(itemId));
const info = await response.json();
if (!response.ok || info.status !== 'success' || info.data.name + '.' + info.data.ext !== path.basename(filePath))
throw new Error('Eagle 尚未同步复合后缀名称,请重新打开素材库');
if (info.data.ext === 'txt') throw new Error('此条目仍按 TXT 登记,请在 Eagle 重新生成缩略图并重新打开素材库');
}
const response = await fetch('http://localhost:41595/api/item/setCustomThumbnail', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -457,16 +466,18 @@
octx.translate(size / 2, size / 2);
octx.scale(scale, -scale);
octx.translate(-cx, -cy);
let offRenderer = renderer;
let offRenderer = null;
try {
offRenderer = clippingRenderer.createRenderer(spine, octx, activeRuntimeDir);
} catch (e) { /* 沿用原 renderer,可能无法正确绑定离屏 ctx */ }
offRenderer.premultipliedAlpha = premultipliedAlpha;
if (offRenderer && 'triangleRendering' in offRenderer) offRenderer.triangleRendering = true;
offRenderer.draw(skeleton);
octx.restore();
offRenderer.premultipliedAlpha = premultipliedAlpha;
offRenderer.draw(skeleton);
} finally {
if (offRenderer && offRenderer.dispose) offRenderer.dispose();
octx.restore();
}
}
await drawSpineBadge(octx, size, size);
require('../js/spine-badge.js').drawVersion(octx, size, size, activeVersion);
// Eagle 的 *_thumbnail.png 通常实际保存 WebP;优先使用 WebP 显著减小体积。
let out = off.toDataURL('image/webp', 0.9);
@@ -487,7 +498,7 @@
// 兼容旧版 Eagle:保留原先的磁盘写入方案,但明确提示可能需要手动刷新列表。
try {
fs.writeFileSync(outPath, buf);
setStatus('缩略图已写入;当前 Eagle 版本未能自动刷新,请按 Cmd/Ctrl+R', 6000);
setStatus('缩略图已写入;自动刷新失败:' + ((e && e.message) || String(e)), 6000);
log('Eagle API 更新失败,已回退写入: ' + ((e && e.message) || String(e)));
} catch (e2) {
setStatus('生成缩略图失败: ' + ((e2 && e2.message) || String(e2)), 6000);
@@ -798,6 +809,27 @@
// ---------- 加载资源并与运行时交互 ----------
async function loadAssets() {
if (!utils.extOf(path.basename(filePath))) {
// Registering txt/bytes is required by Eagle's extension dispatch.
// Ordinary text remains readable and never starts Spine discovery.
document.getElementById('toolbar').style.display = 'none';
document.getElementById('timelineBar').style.display = 'none';
let message = '此文件不是 .atlas.txt 或 .skel.bytes 格式的 Spine 资源。';
if (/\.txt$/i.test(filePath)) {
const fd = fs.openSync(filePath, 'r');
try {
const buffer = Buffer.alloc(256 * 1024);
const count = fs.readSync(fd, buffer, 0, buffer.length, 0);
message = buffer.subarray(0, count).toString('utf8');
if (fs.fstatSync(fd).size > count) message += '\n\n(仅显示前 256 KB)';
} finally { fs.closeSync(fd); }
}
showError(path.basename(filePath), message);
const box = overlay.querySelector('.box');
box.style.cssText = 'max-width:none;width:100%;height:100%;overflow:auto;text-align:left';
document.getElementById('errorTitle').style.color = 'var(--text)';
return;
}
// Eagle 只能按 .json 扩展名把文件交给格式扩展。查看器必须再次检查内容,
// 防止普通 JSON 被拿去匹配库中其他 Spine 骨骼资源。
if (utils.extOf(path.basename(filePath)) === '.json' && utils.classifySkeletonJson(filePath) !== 'spine') {
@@ -814,12 +846,21 @@
}
return;
}
try {
const settings = require('../js/spine-preview-settings.js');
const userData = await settings.resolveDataPath(typeof eagle !== 'undefined' ? eagle.app : null);
previewSettings = settings.createStore(userData, filePath, urlParams.get('id'));
const saved = previewSettings.read();
if (saved !== undefined) premultipliedAlpha = saved;
document.getElementById('pmaCheck').checked = premultipliedAlpha;
} catch (e) { log('读取预乘设置失败: ' + e.message); }
// 检测版本并选择运行时
const version = utils.detectVersion(assets.skeletonPath);
activeVersion = version;
const runtimeDir = utils.chooseRuntimeDir(version, assets.isBinary);
if (!runtimeDir) {
showError('版本不支持', '无法识别的 Spine 版本:' + (version || '未知') + '\n支持的版本:3.6 / 3.7 / 3.8 / 4.0 / 4.1 / 4.2 / 4.3');
showError('版本不支持', '无法识别的 Spine 版本:' + (version || '未知') + '\n支持的版本:3.4 / 3.6 / 3.7 / 3.8 / 4.0 / 4.1 / 4.2 / 4.3');
return;
}
activeRuntimeDir = runtimeDir;
@@ -877,6 +918,7 @@
renderer = clippingRenderer.createRenderer(spine, ctx, runtimeDir);
renderer.premultipliedAlpha = premultipliedAlpha;
document.getElementById('pmaCheck').disabled = false;
fillAnimations();
fillSkins();
@@ -945,7 +987,13 @@
premultipliedAlpha = e.target.checked;
if (renderer) renderer.premultipliedAlpha = premultipliedAlpha;
draw();
setStatus(premultipliedAlpha ? '已开启预乘 Alpha 渲染' : '已关闭预乘 Alpha 渲染', 1800);
try {
if (!previewSettings) throw new Error('素材设置尚未就绪');
previewSettings.write(premultipliedAlpha);
setStatus((premultipliedAlpha ? '预乘已开启' : '预乘已关闭') + ' · 已记住此素材的设置', 1800);
} catch (err) {
setStatus('预乘已切换,但设置保存失败:' + err.message, 5000);
}
});
document.getElementById('reloadBtn').addEventListener('click', () => {
window.location.reload();