新增3.4版本支持
This commit is contained in:
1 parent
4c5033d2f9
commit
9635428f78
21 files changed
+5425
-51
No files matched your search
+71
-7
@@ -22,7 +22,7 @@ const renderJobs = new Map();
|
||||
const refreshJobs = new Map();
|
||||
|
||||
/** 插件代码版本:每次改动递增,用于确认 Eagle 是否加载到最新代码 */
|
||||
const VERSION = 'v11';
|
||||
const VERSION = 'v13';
|
||||
|
||||
function coordinationKey(src) {
|
||||
const root = utils.findLibraryRoot(src) || path.dirname(path.dirname(src));
|
||||
@@ -125,7 +125,37 @@ async function renderThumbnailShared(assets, options) {
|
||||
* - dest : 需要生成的缩略图 PNG 路径
|
||||
* - item : 当前 item 对象(可更新宽高)
|
||||
*/
|
||||
module.exports = async ({ src, dest, item }) => {
|
||||
module.exports = async (options) => {
|
||||
const { src, dest, item } = options;
|
||||
const changed = require('../js/eagle-compound.js').normalizeItem(src, item);
|
||||
// Eagle originally chooses the cache name before invoking the plugin.
|
||||
// Supply that requested output as well as the corrected compound-name cache.
|
||||
const canonical = changed && path.basename(dest).endsWith('_thumbnail.png')
|
||||
? path.join(path.dirname(dest), item.name + '_thumbnail.png') : dest;
|
||||
const result = await generate({ ...options, deferRefresh: changed });
|
||||
if (canonical !== dest && fs.existsSync(dest)) {
|
||||
if (render.canvasLib) {
|
||||
const image = await render.canvasLib.loadImage(dest);
|
||||
const canvas = render.canvasLib.createCanvas(image.width, image.height);
|
||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
||||
fs.writeFileSync(canonical, await canvas.encode('webp', 90));
|
||||
} else fs.copyFileSync(dest, canonical);
|
||||
}
|
||||
if (changed && item.id) {
|
||||
log('复合后缀元数据已规范化: name=' + item.name + ' ext=' + item.ext);
|
||||
// The item does not enter Eagle's item map until this callback returns.
|
||||
// Reconcile afterwards, using the correctly named persistent cache.
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const assets = utils.discoverAssets(src, { strictStem: true, allowCrossBatch: true });
|
||||
if (assetSignature(assets)) refreshRelatedThumbnails(assets, canonical, item.id).catch(e => log(String(e)));
|
||||
} catch (e) { log('复合后缀补刷失败: ' + String(e)); }
|
||||
}, 1500);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
async function generate({ src, dest, item, deferRefresh }) {
|
||||
try {
|
||||
// 记录 Eagle 传入的关键信息,便于诊断 src/item 的实际形态
|
||||
log('HIT ' + VERSION + ' src=' + src + ' (isTexture=' + utils.isTextureFile(src) + ')');
|
||||
@@ -143,6 +173,20 @@ module.exports = async ({ src, dest, item }) => {
|
||||
// —— 防御:若 src 是图片(png/jpg/jpeg/webp),不生成占位图,
|
||||
// 而是把原始图片复制/转换为缩略图,交由系统展示真实内容。
|
||||
const srcExt = utils.extOf(path.basename(src));
|
||||
// Eagle dispatches compound suffixes as txt/bytes. Only the complete
|
||||
// .atlas.txt/.skel.bytes names enter Spine discovery; ordinary files exit.
|
||||
if (!srcExt) {
|
||||
if (/\.txt$/i.test(src)) {
|
||||
// Eagle's native text card requires item.text, even for an empty file.
|
||||
item.text = '';
|
||||
const fd = fs.openSync(src, 'r');
|
||||
try { const buffer = Buffer.alloc(32768); item.text = buffer.subarray(0, fs.readSync(fd, buffer, 0, buffer.length, 0)).toString('utf8'); }
|
||||
finally { fs.closeSync(fd); }
|
||||
}
|
||||
await writeNeutralJsonPlaceholder(dest);
|
||||
item.width = 400; item.height = 400;
|
||||
return resolve(item);
|
||||
}
|
||||
if (utils.isTextureFile(src) || srcExt === '.png' || srcExt === '.jpg' || srcExt === '.jpeg' || srcExt === '.webp') {
|
||||
await copyImageAsThumb(src, dest);
|
||||
try { const s = imageSizeOf(dest); item.width = s.width || item.width; item.height = s.height || item.height; } catch (e) {}
|
||||
@@ -181,7 +225,7 @@ module.exports = async ({ src, dest, item }) => {
|
||||
|
||||
if (!runtimeDir) {
|
||||
if (await writeAtlasFallback(assets, dest)) {
|
||||
await refreshRelatedThumbnails(assets, dest, item && item.id);
|
||||
if (!deferRefresh) await refreshRelatedThumbnails(assets, dest, item && item.id);
|
||||
item.width = 400; item.height = 400;
|
||||
return resolve(item);
|
||||
}
|
||||
@@ -219,7 +263,7 @@ module.exports = async ({ src, dest, item }) => {
|
||||
if (!png) {
|
||||
if (await writeAtlasFallback(assets, dest)) {
|
||||
log('骨骼无法解析,已使用 atlas 贴图缩略图: ' + src);
|
||||
await refreshRelatedThumbnails(assets, dest, item && item.id);
|
||||
if (!deferRefresh) await refreshRelatedThumbnails(assets, dest, item && item.id);
|
||||
item.width = 400; item.height = 400;
|
||||
return resolve(item);
|
||||
}
|
||||
@@ -227,7 +271,7 @@ module.exports = async ({ src, dest, item }) => {
|
||||
}
|
||||
|
||||
fs.writeFileSync(dest, png);
|
||||
await refreshRelatedThumbnails(assets, dest, item && item.id);
|
||||
if (!deferRefresh) await refreshRelatedThumbnails(assets, dest, item && item.id);
|
||||
|
||||
try {
|
||||
const size = imageSizeOf(dest);
|
||||
@@ -262,7 +306,22 @@ function itemIdFromAssetPath(filePath) {
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function setEagleCustomThumbnail(itemId, thumbnailPath) {
|
||||
async function setEagleCustomThumbnail(itemId, thumbnailPath, expectedFilename) {
|
||||
if (expectedFilename) {
|
||||
// Eagle's foreground may still hold the old name after a type change.
|
||||
// Do not let setCustomThumbnail persist that stale name back to metadata.
|
||||
const info = await new Promise((resolveInfo, rejectInfo) => {
|
||||
const req = http.get('http://127.0.0.1:41595/api/item/info?id=' + encodeURIComponent(itemId), response => {
|
||||
let body = ''; response.on('data', chunk => { body += chunk; });
|
||||
response.on('end', () => {
|
||||
try { const result = JSON.parse(body); if (result.status !== 'success') throw new Error('Eagle 素材尚未入库'); resolveInfo(result.data); }
|
||||
catch (error) { rejectInfo(error); }
|
||||
});
|
||||
});
|
||||
req.setTimeout(3000, () => req.destroy(new Error('Eagle 素材信息请求超时'))); req.on('error', rejectInfo);
|
||||
});
|
||||
if (!info || info.name + '.' + info.ext !== expectedFilename) throw new Error('Eagle 尚未同步复合后缀名称,请重新打开素材库后刷新缩略图');
|
||||
}
|
||||
return new Promise((resolveRequest, rejectRequest) => {
|
||||
const body = JSON.stringify({ id: itemId, thumbnailPath });
|
||||
const request = http.request({
|
||||
@@ -280,6 +339,7 @@ function setEagleCustomThumbnail(itemId, thumbnailPath) {
|
||||
});
|
||||
});
|
||||
request.on('error', rejectRequest);
|
||||
request.setTimeout(12000, () => request.destroy(new Error('Eagle 缩略图请求超时')));
|
||||
request.end(body);
|
||||
});
|
||||
}
|
||||
@@ -307,7 +367,10 @@ async function refreshRelatedThumbnails(assets, thumbnailPath, currentItemId) {
|
||||
let apiPath = thumbnailPath;
|
||||
try {
|
||||
apiPath = await createWebpCopy(thumbnailPath, utils.normalizeStem(path.basename(assets.skeletonPath || assets.atlasPath)));
|
||||
const results = await Promise.allSettled(ids.map((id) => setEagleCustomThumbnail(id, apiPath)));
|
||||
const results = await Promise.allSettled(ids.map((id) => {
|
||||
const source = (assets.relatedDataPaths || []).find(file => itemIdFromAssetPath(file) === id);
|
||||
return setEagleCustomThumbnail(id, apiPath, source && path.basename(source));
|
||||
}));
|
||||
const failed = results.filter((result) => result.status === 'rejected');
|
||||
if (failed.length) throw failed[0].reason;
|
||||
log('统一补刷完成: ' + ids.length + ' 个条目 [' + ids.join(',') + ']');
|
||||
@@ -458,6 +521,7 @@ async function writeAtlasFallback(assets, dest) {
|
||||
const h = Math.max(1, Math.round(img.height * scale));
|
||||
ctx.drawImage(img, Math.round((size - w) / 2), Math.round((size - h) / 2), w, h);
|
||||
await render.drawSpineBadge(ctx, size, size);
|
||||
if (assets.skeletonPath) require('../js/spine-badge.js').drawVersion(ctx, size, size, utils.detectVersion(assets.skeletonPath));
|
||||
fs.writeFileSync(dest, canvas.toBuffer('image/png'));
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
||||
Reference in new issue
Block a user