fix(models): 默认模型改用「压缩包 sha256」匹配(转换产物非字节级确定)
实测发现:同一份 zip 连续转换三次得到三个不同的产物 sha256(大小都是 1,152,464 字节,差异集中在 BIN chunk 的十几字节),说明 Assimp 的 GLB 导出不是字节级确定的。 因此原先把「转换产物 sha256」当作默认模型匹配键的设计是错的——换一次转换就失效。 - default-model.ts / default-model-source.ts:种子拆成 sourceSha256(上传压缩包的 sha256,稳定,主匹配键)与 fileSha256(产物 sha256,仅作记录) - models.service.ts:resolveDefaultModelId 依次按 压缩包 sha256 → 产物 sha256 → 名称 匹配,都落空才回落到 VRM 优先规则并告警 - ModelDto 增加 sourceSha256(非压缩包上传为 null) - scripts/import-default-model.mjs:写种子时用 sourceSha256;顺带修掉位置参数解析 (文件当第一个参数时识别不出,会打印用法) - 契约与文档同步:data-models §4.1、model-formats §1.1 记录该确定性结论; README / DSH / tasks/005 的「按产物 sha256 匹配」表述已更正 另附:把模型格式能力的实测矩阵写进 Release 说明(原生只支持 glTF 系; PMX→FBX 失败;VRM→GLB 丢 VRM 扩展;humanoid 骨骼命名覆盖率)。 验证:125 项单测通过;清理掉两个"文件只在本机临时目录"的模型(避免 sha256 去重 导致后续上传不落盘);种子按压缩包 sha256 命中后 defaultModelId 正确解析。
This commit is contained in:
@@ -41,8 +41,33 @@ const flag = (name, fallback = undefined) => {
|
||||
};
|
||||
const has = (name) => args.includes(name);
|
||||
|
||||
const positional = args.find((a) => !a.startsWith('--') && args[args.indexOf(a) - 1]?.startsWith('--') === false);
|
||||
const file = positional;
|
||||
/** 需要跟一个取值的开关(其余 --x 视为布尔开关) */
|
||||
const VALUE_FLAGS = new Set([
|
||||
'--url',
|
||||
'--name',
|
||||
'--author',
|
||||
'--license',
|
||||
'--source-name',
|
||||
'--source-url',
|
||||
'--source-type',
|
||||
'--model-id',
|
||||
'--dir',
|
||||
'--env-file',
|
||||
]);
|
||||
|
||||
/** 第一个"不是开关、也不是开关取值"的参数即输入文件 */
|
||||
function positionalFile(argv) {
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (arg.startsWith('--')) {
|
||||
if (VALUE_FLAGS.has(arg)) i += 1;
|
||||
continue;
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const file = positionalFile(args);
|
||||
|
||||
const url = String(flag('--url', 'http://127.0.0.1:3000')).replace(/\/$/, '');
|
||||
const writeSourceOnly = has('--write-source-only');
|
||||
@@ -118,8 +143,8 @@ async function main() {
|
||||
console.log(`转换说明:${model.conversionNote ?? '(无)'}`);
|
||||
}
|
||||
|
||||
if (!model.fileSha256) {
|
||||
console.error('该模型没有 fileSha256(文件缺失?),无法写入默认模型种子');
|
||||
if (!model.fileSha256 && !model.sourceSha256) {
|
||||
console.error('该模型没有 fileSha256 / sourceSha256(文件缺失?),无法写入默认模型种子');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -127,10 +152,16 @@ async function main() {
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
format: model.format,
|
||||
sha256: model.fileSha256,
|
||||
// 主匹配键用上传压缩包的 sha256(稳定);直接上传模型文件时退回产物 sha256
|
||||
sourceSha256: model.sourceSha256 ?? model.fileSha256,
|
||||
fileSha256: model.fileSha256 ?? '',
|
||||
convertedFrom: (model.conversionNote ?? '').split(',')[0] || null,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
console.log(` 种子匹配键:sourceSha256=${String(seed.sourceSha256).slice(0, 16)}…`);
|
||||
if (model.sourceSha256) {
|
||||
console.log(' (用的是压缩包 sha256:Assimp 转换产物每次都不一样,只有它稳定)');
|
||||
}
|
||||
const content = renderDefaultModelSource(seed);
|
||||
|
||||
if (dryRun) {
|
||||
|
||||
Reference in New Issue
Block a user