优化识别
This commit is contained in:
1 parent
7263a841cb
commit
5cb2e76471
9 files changed
+302
-66
No files matched your search
+95
-25
@@ -142,6 +142,21 @@ function btimeOf(fp) {
|
||||
return (d && d.btime) || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagle item 的实际入库时间。
|
||||
* metadata.json 的 birthtime 在生成缩略图、修改标签后也不会改变,比素材自身的
|
||||
* btime/mtime 更适合判断多个独立 .info 项是否来自同一次拖入。
|
||||
*/
|
||||
function importTimeOf(fp) {
|
||||
try {
|
||||
const metaPath = path.join(path.dirname(fp), 'metadata.json');
|
||||
const stat = fs.statSync(metaPath);
|
||||
return stat.birthtimeMs || stat.ctimeMs || stat.mtimeMs || 0;
|
||||
} catch (e) {
|
||||
return btimeOf(fp);
|
||||
}
|
||||
}
|
||||
|
||||
/** 骨骼版本所属家族(3x / 4x),atlas 格式按版本家族区分 */
|
||||
function versionFamily(version) {
|
||||
if (!version) return null;
|
||||
@@ -343,7 +358,10 @@ function resolveRealSource(filePath) {
|
||||
* 返回 { skeletonPath, atlasPath, texturePaths, base, isBinary, ... }
|
||||
* - texturePaths: { atlas 中引用的贴图名 -> 绝对路径 }
|
||||
*/
|
||||
function discoverAssets(filePath) {
|
||||
function discoverAssets(filePath, options) {
|
||||
options = options || {};
|
||||
const strictStem = !!options.strictStem;
|
||||
const preferCompatibleJson = !!options.preferCompatibleJson;
|
||||
filePath = resolveRealSource(filePath);
|
||||
const selected = path.basename(filePath);
|
||||
const selectedStem = normalizeStem(selected);
|
||||
@@ -353,9 +371,11 @@ function discoverAssets(filePath) {
|
||||
// 则扩展搜索到整个库的 images/ 目录(全库兜底,靠“同家族 + 同文件夹 + 最近 btime”配对)。
|
||||
buildRootsWithLibraryRoot(filePath, roots);
|
||||
|
||||
// 只匹配选中文件所在的 Eagle 文件夹(及子文件夹)内的资源,避免串到其他项目
|
||||
// 导入时 Eagle 会先创建各个 .info,再异步写 folders。严格模式必须把尚未写入
|
||||
// folders 的同批资源也纳入候选,之后用 metadata.json 的 birthtime 精确配对。
|
||||
const validFolders = getValidFolders(filePath);
|
||||
const files = collectSpineFiles(roots, validFolders);
|
||||
let files = collectSpineFiles(roots, validFolders);
|
||||
if (strictStem) files = collectSpineFiles(roots, null);
|
||||
|
||||
const skeletonFiles = files.filter((f) => isSkeletonFile(f.name));
|
||||
const atlasFiles = files.filter((f) => isAtlasFile(f.name));
|
||||
@@ -367,22 +387,29 @@ function discoverAssets(filePath) {
|
||||
return true;
|
||||
});
|
||||
|
||||
// 工具:优先选择与 refFile 位于同一个 Eagle 逻辑文件夹的候选;
|
||||
// 同文件夹内有多个时,再按“创建时间 btime 最接近”选取(视为同一批导入)。
|
||||
// 工具:优先选择与 refFile 同一批入库的候选,再看 Eagle 逻辑文件夹及素材时间。
|
||||
// 这可区分同一文件夹中多次导入的同名 Spine 导出,又不受素材原始时间影响。
|
||||
const selectBest = (cands, refFile) => {
|
||||
if (!cands || cands.length === 0) return null;
|
||||
const refFolders = foldersOfItem(refFile);
|
||||
const refImportTime = importTimeOf(refFile);
|
||||
const refBtime = btimeOf(refFile);
|
||||
const scored = cands.map((c) => {
|
||||
let score = 0;
|
||||
const ranked = cands.map((c) => {
|
||||
const cFolders = foldersOfItem(c.fullPath);
|
||||
if (refFolders.length && cFolders.some((f) => refFolders.indexOf(f) !== -1)) score += 1e6;
|
||||
// btime 越接近越好(差值作为负加成)
|
||||
score += -Math.abs(btimeOf(c.fullPath) - refBtime);
|
||||
return { c, score };
|
||||
return {
|
||||
c,
|
||||
importDelta: refImportTime ? Math.abs(importTimeOf(c.fullPath) - refImportTime) : Number.MAX_SAFE_INTEGER,
|
||||
sameFolder: refFolders.length && cFolders.some((f) => refFolders.indexOf(f) !== -1) ? 1 : 0,
|
||||
btimeDelta: refBtime ? Math.abs(btimeOf(c.fullPath) - refBtime) : Number.MAX_SAFE_INTEGER,
|
||||
};
|
||||
});
|
||||
scored.sort((a, b) => b.score - a.score);
|
||||
return scored[0].c;
|
||||
ranked.sort((a, b) =>
|
||||
a.importDelta - b.importDelta ||
|
||||
b.sameFolder - a.sameFolder ||
|
||||
a.btimeDelta - b.btimeDelta ||
|
||||
a.c.fullPath.localeCompare(b.c.fullPath)
|
||||
);
|
||||
return ranked[0].c;
|
||||
};
|
||||
|
||||
// 辅助:读取候选 atlas 的格式家族,以及候选骨骼的版本家族
|
||||
@@ -398,7 +425,8 @@ function discoverAssets(filePath) {
|
||||
let skeleton = null;
|
||||
// 若选中的就是骨骼文件,务必使用它本身(它的版本即最终版本)
|
||||
if (isSkeletonFile(selected)) {
|
||||
skeleton = realSkeletonFiles.find((f) => f.name === selected) || null;
|
||||
skeleton = realSkeletonFiles.find((f) => f.fullPath === filePath) ||
|
||||
realSkeletonFiles.find((f) => f.name === selected) || null;
|
||||
}
|
||||
if (!skeleton) {
|
||||
// 依选中 stem 匹配;优先与选中 atlas 同家族(3x/4x),再按同文件夹/同批导入
|
||||
@@ -410,19 +438,43 @@ function discoverAssets(filePath) {
|
||||
skeleton = cands.length > 0 ? selectBest(cands, filePath) : null;
|
||||
}
|
||||
if (!skeleton && realSkeletonFiles.length > 0) {
|
||||
// 忽略 stem:优先与选中 atlas 同家族,再按同 Eagle 文件夹 + 最近 btime 选取
|
||||
let cands = realSkeletonFiles;
|
||||
if (selectedAtlasFamily) {
|
||||
const same = cands.filter((f) => familyOfSkel(f) === selectedAtlasFamily);
|
||||
if (same.length > 0) cands = same;
|
||||
if (strictStem) {
|
||||
skeleton = null;
|
||||
} else {
|
||||
// 忽略 stem:优先与选中 atlas 同家族,再按入库批次选取
|
||||
let cands = realSkeletonFiles;
|
||||
if (selectedAtlasFamily) {
|
||||
const same = cands.filter((f) => familyOfSkel(f) === selectedAtlasFamily);
|
||||
if (same.length > 0) cands = same;
|
||||
}
|
||||
skeleton = selectBest(cands, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Spine 3.6/3.7 的官方 JS 运行时不能读取对应二进制格式。若同批导入中
|
||||
// 同时带有 JSON,则 atlas 和 skel 条目都用该 JSON 渲染真实缩略图。
|
||||
if (preferCompatibleJson && skeleton && isSkeletonFile(skeleton.name)) {
|
||||
let selectedVersion = null;
|
||||
try { selectedVersion = detectVersion(skeleton.fullPath); } catch (e) { /* ignore */ }
|
||||
const isOldBinary = /\.skel(?:\.bytes)?$/i.test(skeleton.name) && selectedVersion && parseFloat(selectedVersion) < 3.8;
|
||||
if (isOldBinary) {
|
||||
const stem = normalizeStem(skeleton.name);
|
||||
const jsonCands = realSkeletonFiles.filter((f) =>
|
||||
f.name.toLowerCase().endsWith('.json') &&
|
||||
normalizeStem(f.name) === stem &&
|
||||
familyOfSkel(f) === familyOfSkel(skeleton) &&
|
||||
(!strictStem || Math.abs(importTimeOf(f.fullPath) - importTimeOf(skeleton.fullPath)) <= 30000)
|
||||
);
|
||||
const jsonFallback = selectBest(jsonCands, skeleton.fullPath);
|
||||
if (jsonFallback) skeleton = jsonFallback;
|
||||
}
|
||||
skeleton = selectBest(cands, filePath);
|
||||
}
|
||||
|
||||
// 2) atlas 文件 —— 优先与骨骼版本家族一致(3x/4x),避免 3.8 骨骼配 4x atlas
|
||||
let atlas = null;
|
||||
if (isAtlasFile(selected)) {
|
||||
atlas = atlasFiles.find((f) => f.name === selected) || null;
|
||||
atlas = atlasFiles.find((f) => f.fullPath === filePath) ||
|
||||
atlasFiles.find((f) => f.name === selected) || null;
|
||||
}
|
||||
// 统一的候选选择:优先“与骨骼同家族(3x/4x)”的 atlas,再按“同文件夹 + 最近 btime”挑选。
|
||||
// 家族过滤无条件优先,避免 4x 骨骼配到 3x 图集(会导致 UV/坐标错乱)。
|
||||
@@ -446,10 +498,24 @@ function discoverAssets(filePath) {
|
||||
atlas = pickAtlas(cands, skeleton.fullPath);
|
||||
}
|
||||
if (!atlas && skeleton) {
|
||||
// 忽略 stem:用与骨骼同 Eagle 文件夹 + 同家族(3x/4x) + 最近 btime 的 atlas
|
||||
atlas = pickAtlas(atlasFiles, skeleton.fullPath);
|
||||
if (strictStem) {
|
||||
atlas = null;
|
||||
} else {
|
||||
// 忽略 stem:用与骨骼同家族且同批入库的 atlas
|
||||
atlas = pickAtlas(atlasFiles, skeleton.fullPath);
|
||||
}
|
||||
}
|
||||
if (!atlas && !strictStem && atlasFiles.length === 1) atlas = atlasFiles[0];
|
||||
|
||||
// 严格模式用于导入缩略图:配套骨骼和图集应由同一次拖入创建。若时间差
|
||||
// 过大,说明新同伴还没落盘,返回缺失让调用方继续等待,不能借用旧同名资源。
|
||||
if (strictStem && skeleton && atlas) {
|
||||
const pairDelta = Math.abs(importTimeOf(skeleton.fullPath) - importTimeOf(atlas.fullPath));
|
||||
if (pairDelta > 30000) {
|
||||
if (isAtlasFile(selected)) skeleton = null;
|
||||
else atlas = null;
|
||||
}
|
||||
}
|
||||
if (!atlas && atlasFiles.length === 1) atlas = atlasFiles[0];
|
||||
|
||||
// 3) 解析 atlas 引用的贴图路径(多个同名贴图时,优先与 atlas 同文件夹/同批导入的)
|
||||
let texturePaths = {};
|
||||
@@ -468,7 +534,8 @@ function discoverAssets(filePath) {
|
||||
// 2) 否则在候选里按“同 Eagle 文件夹 + 最近 btime”挑选
|
||||
let cands = textureFiles.filter((f) => f.name.toLowerCase() === pn.toLowerCase());
|
||||
if (cands.length === 0) cands = textureFiles.filter((f) => normalizeStem(f.name) === normalizeStem(pn));
|
||||
const hit = selectBest(cands, atlas.fullPath);
|
||||
let hit = selectBest(cands, atlas.fullPath);
|
||||
if (strictStem && hit && Math.abs(importTimeOf(hit.fullPath) - importTimeOf(atlas.fullPath)) > 30000) hit = null;
|
||||
if (hit) texturePaths[pn] = hit.fullPath;
|
||||
}
|
||||
}
|
||||
@@ -484,7 +551,9 @@ function discoverAssets(filePath) {
|
||||
base,
|
||||
isBinary: skeletonIsBinary,
|
||||
skeletonCandidates: realSkeletonFiles.map((f) => f.name),
|
||||
skeletonCandidatePaths: realSkeletonFiles.map((f) => f.fullPath),
|
||||
atlasCandidates: atlasFiles.map((f) => f.name),
|
||||
atlasCandidatePaths: atlasFiles.map((f) => f.fullPath),
|
||||
textureCandidates: textureFiles.map((f) => f.name),
|
||||
};
|
||||
}
|
||||
@@ -535,6 +604,7 @@ module.exports = {
|
||||
getValidFolders,
|
||||
mtimeOf,
|
||||
btimeOf,
|
||||
importTimeOf,
|
||||
versionFamily,
|
||||
atlasFamily,
|
||||
itemMeta,
|
||||
|
||||
Reference in new issue
Block a user