From 8aa71244b6f67c3304b268a5a6628ad35534da56 Mon Sep 17 00:00:00 2001 From: L1Z0 <2grif484d@mozmail.com> Date: Thu, 1 Oct 2026 02:00:33 +0200 Subject: [PATCH] tweak --- AnimationDirector.js | 12 +++++++++--- test/model-variants.test.js | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/AnimationDirector.js b/AnimationDirector.js index edd1872..9b602d4 100644 --- a/AnimationDirector.js +++ b/AnimationDirector.js @@ -2415,6 +2415,11 @@ return sanitizeFilePart(String(name).replace(/\.geo\.json$/i, '').replace(/\.json$/i, '')) || 'model'; } + function normalizeGeckoExportName(name) { + return String(name || '').trim().replace(/\.(?:bbmodel|bbench)$/i, '') + .replace(/\.json$/i, '').replace(/(?:\.geo)+$/i, ''); + } + async function cemTemplateCatalog() { if (typeof window === 'undefined' || typeof window.loadCEMTemplateModels !== 'function') { throw new Error('Install and enable CEM Template Loader before creating a JEM model.'); @@ -2909,8 +2914,9 @@ const bones = jem ? jemVariantBones(output) : [...new Map((output['minecraft:geometry'] || []) .flatMap((geometry) => geometry.bones || []).map((bone) => [bone.name, bone])).values()]; const form = { - name: { type: 'text', label: 'Base model filename', value: saved.name || (jem - ? getModelExportFileName().replace(/\.jem$/i, '').replace(/[123]$/, '') : getModelExportFileName().replace(/\.(m|f|mf)$/i, '')) }, + name: { type: 'text', label: 'Base model filename', value: jem + ? saved.name || getModelExportFileName().replace(/\.jem$/i, '').replace(/[123]$/, '') + : normalizeGeckoExportName(saved.name || normalizeGeckoExportName(getModelExportFileName()).replace(/\.(m|f|mf)$/i, '')) }, base: { type: 'checkbox', label: `Export unqualified model (.${extension})`, value: saved.base ?? true }, info: { type: 'info', text: 'Export one or more variants from this model. Excluded bones and their descendants are completely omitted, including geometry and locators. The source project is unchanged.' }, }; @@ -2930,7 +2936,7 @@ onConfirm(result) { try { if (Project !== sourceProject) throw new Error('Return to the source model project and retry.'); - const name = String(result.name || '').trim().replace(jem ? /\.jem$/i : /\.geo\.json$/i, ''); + const name = jem ? String(result.name || '').trim().replace(/\.jem$/i, '') : normalizeGeckoExportName(result.name); if (!name || /[<>:"/\\|?*]/.test(name)) throw new Error('Choose a valid base model filename without a folder path.'); const genders = ['m', 'f', 'mf'].filter((gender) => result[gender]); if (!result.base && !genders.length) throw new Error('Select at least one model to export.'); diff --git a/test/model-variants.test.js b/test/model-variants.test.js index 5d483c1..ba663bc 100644 --- a/test/model-variants.test.js +++ b/test/model-variants.test.js @@ -7,12 +7,13 @@ const vm = require('node:vm'); function variantBuilder() { const source = fs.readFileSync(path.join(__dirname, '..', 'AnimationDirector.js'), 'utf8').replace( ' Plugin.register(PLUGIN_ID, {', - ' globalThis.variantTest = { buildGenderModelVariant, refreshModelVariantFields, createModelVariantBoneSections, buildJemModelVariant };\n Plugin.register(PLUGIN_ID, {' + ' globalThis.variantTest = { buildGenderModelVariant, refreshModelVariantFields, createModelVariantBoneSections, buildJemModelVariant, normalizeGeckoExportName };\n Plugin.register(PLUGIN_ID, {' ); const context = { Plugin: { register() {} }, console }; vm.runInNewContext(source, context); return Object.assign(context.variantTest.buildGenderModelVariant, { refresh: context.variantTest.refreshModelVariantFields, - sections: context.variantTest.createModelVariantBoneSections, jem: context.variantTest.buildJemModelVariant }); + sections: context.variantTest.createModelVariantBoneSections, jem: context.variantTest.buildJemModelVariant, + normalizeName: context.variantTest.normalizeGeckoExportName }); } test('gender variants omit selected bones, descendants, locators and texture overrides without changing source', () => { @@ -35,6 +36,15 @@ test('gender variants omit selected bones, descendants, locators and texture ove assert.equal(JSON.stringify(source), before); }); +test('GeckoLib export filenames strip repeated geo and project extensions', () => { + const normalize = variantBuilder().normalizeName; + for (const name of ['horse', 'horse.geo', 'horse.geo.geo', 'horse.geo.json', 'horse.geo.bbench', 'horse.geo.bbmodel']) { + assert.equal(normalize(name), 'horse'); + } + assert.equal(normalize('horse.f.geo.geo.json'), 'horse.f'); + assert.equal(normalize('geology'), 'geology'); +}); + test('variant subtree propagation handles nested children and unknown saved selections', () => { const build = variantBuilder(); const source = { 'minecraft:geometry': [{ bones: [