improvements

This commit is contained in:
2026-09-29 00:43:09 +02:00
parent 0bceb63671
commit 6d4094e7c4
2 changed files with 103 additions and 33 deletions
+55 -26
View File
@@ -4978,9 +4978,10 @@
[id, `${id}/${id.split('/').pop()}`], '.animation.json') };
}
function importDialog(title, form, confirmText = 'Continue') {
function importDialog(title, form, confirmText = 'Continue', onFormChange = null) {
return new Promise((resolve) => new Dialog({ id: 'animationdirector_automatic_import_step', title, width: 680, form,
buttons: ['Cancel', confirmText], cancelIndex: 0, confirmIndex: 1,
...(onFormChange ? { onFormChange } : {}),
onConfirm(result) { resolve(result); }, onCancel() { resolve(null); } }).show());
}
@@ -5036,15 +5037,14 @@
function loadImportTextures(row, packs, io) {
const candidates = actorTextureCandidates(row.json, row.geo, row.resolved.file, row.entity, row.slim);
const ids = new Set([...candidates, ...Object.values(row.json.afw_bone_textures || {})
.filter((value) => typeof value === 'string')]);
row.textures = new Map();
row.defaultTexture = undefined;
let cachedSources;
let modelPack;
try { modelPack = findImportPack(row.resolved.file, io); } catch (_) { /* Standalone model. */ }
const searchPacks = modelPack && !packs.some((pack) => pack.root === modelPack.root)
? [modelPack, ...packs] : packs;
for (const resource of ids) {
const resolveTexture = (resource) => {
const id = resource.includes(':') ? resource : `minecraft:${resource}`;
const texture = findImportResource(searchPacks, id, '', '', io);
if (texture) {
@@ -5057,8 +5057,7 @@
dimensions = importTextureDimensions(bytes, metadata);
}
} catch (_) { /* Let Blockbench report unreadable texture files. */ }
row.textures.set(resource, { ...texture, dimensions });
continue;
return { ...texture, dimensions };
}
if (!cachedSources) {
cachedSources = [];
@@ -5067,7 +5066,7 @@
}
}
const parsed = importResourceId(id);
if (!parsed) continue;
if (!parsed) return null;
const relative = `assets/${parsed.namespace}/${parsed.resource}`;
for (const source of cachedSources) {
let bytes;
@@ -5077,11 +5076,22 @@
if (!dimensions) continue;
try { dimensions = importTextureDimensions(bytes, propJson(source, `${relative}.mcmeta`)); }
catch (_) { /* Keep the PNG dimensions when metadata is invalid. */ }
row.textures.set(resource, { bytes, relative, dimensions });
break;
return { bytes, relative, dimensions };
}
return null;
};
for (const resource of candidates) {
const texture = resolveTexture(resource);
if (!texture) continue;
row.textures.set(resource, texture);
row.defaultTexture = resource;
break;
}
for (const resource of Object.values(row.json.afw_bone_textures || {})) {
if (typeof resource !== 'string' || row.textures.has(resource)) continue;
const texture = resolveTexture(resource);
if (texture) row.textures.set(resource, texture);
}
row.defaultTexture = candidates.find((id) => row.textures.has(id));
}
function commitAutomaticImport(plan) {
@@ -5187,7 +5197,10 @@
const available = parsed.actorLabels.find((candidate) => !usedClips.has(candidate));
if (available) { clipRoles[index] = available; usedClips.add(available); }
});
const form = { info: { type: 'info', text: `### ${companions.id}\nImport into a NEW GeckoLib project. Choose concrete actors and confirm their clip roles. Use Manual Import > Pack Folders to register shared/default packs before importing.\nFolder priority: ${registry.packs.map((pack) => pack.name).join(' > ')}` } };
const form = {
info: { type: 'info', text: `### ${companions.id}\nImport into a NEW GeckoLib project. Use Manual Import > Pack Folders to register shared/default packs before importing.\nFolder priority: ${registry.packs.map((pack) => pack.name).join(' > ')}` },
actors_section: { type: 'info', text: '### Actors\nChoose concrete actors and confirm their animation roles.' },
};
definition.actors.forEach((actor, index) => {
const entities = (actor.entity_types || []).filter((id) => importResourceId(id));
form[`heading_${index}`] = { type: 'info', text: `### Actor ${index + 1}: ${keys[index]}` };
@@ -5205,7 +5218,28 @@
if (!entities.length || entities.includes('minecraft:player')) form[`slim_${index}`] = { type: 'checkbox', label: 'Slim player model', value: false,
condition: (result) => result[`entity_${index}`] === 'minecraft:player' };
});
const choices = await importDialog('Automatic Import: Actors', form);
form.models_section = { type: 'info', text: '### Actor Models\nModels are selected automatically from the actor choices above, following local, imported, old default revision, and shared priority. Select a file only to override that lookup or supply a missing model.' };
const modelStatus = (index, values) => {
const entity = values[`entity_${index}`], gender = values[`gender_${index}`];
if (!importResourceId(entity) || !['m', 'f', 'mf'].includes(gender)) return 'Choose an entity and model gender above.';
try {
const resolved = resolveImportModel(source, registry.packs, entity, gender,
definition.actors[index].entity_variant, io, !!values[`slim_${index}`]);
return resolved ? `${resolved.kind}: ${resolved.file}` : 'No automatic model found. Select a model file below.';
} catch (error) { return `Model lookup unavailable: ${error.message}`; }
};
const defaults = Object.fromEntries(Object.entries(form).filter(([, field]) => 'value' in field)
.map(([key, field]) => [key, field.value]));
definition.actors.forEach((_, index) => {
form[`source_${index}`] = { type: 'text', label: `Actor ${index + 1} automatic model`,
value: modelStatus(index, defaults), readonly: true, full_width: true };
form[`model_${index}`] = { type: 'file', label: `Actor ${index + 1} model override`,
return_as: 'file', extensions: ['json'], readtype: 'text', filetype: 'GeckoLib geometry' };
});
const choices = await importDialog('Automatic Import: Actors & Models', form, 'Create Project and Import', function(result) {
this.form.setValues(Object.fromEntries(definition.actors.map((_, index) =>
[`source_${index}`, modelStatus(index, result)])), false);
});
if (!choices) return;
const rows = definition.actors.map((actor, index) => ({ actor, label: choices[`clip_${index}`], entity: choices[`entity_${index}`], gender: choices[`gender_${index}`], slim: !!choices[`slim_${index}`] }));
if (new Set(rows.map((row) => row.label)).size !== rows.length || rows.some((row) => !parsed.actorLabels.includes(row.label))) {
@@ -5244,18 +5278,11 @@
const prepared = prepareAnimationDefinitionImport(relabeled, analysis);
const warnings = registry.warnings.slice();
if (ignoredLabels.length) warnings.push(`Ignored animation actors not mapped to the definition: ${ignoredLabels.join(', ')}.`);
const models = { info: { type: 'info', text: '### Actor models\nModels follow local, declared imports, old default revisions, and shared fallbacks. Choose a replacement only when necessary; missing models must be selected manually.' } };
rows.forEach((row, index) => {
row.resolved = resolveImportModel(source, registry.packs, row.entity, row.gender, row.actor.entity_variant, io, row.slim);
models[`source_${index}`] = { type: 'info', text: `### ${row.label}\n${row.resolved ? `${row.resolved.kind}: ${row.resolved.file}` : 'Model not found. Select a geometry file below.'}` };
models[`model_${index}`] = { type: 'file', label: row.resolved ? 'Override model (optional)' : 'Select model (required)',
return_as: 'file', extensions: ['json'], readtype: 'text', filetype: 'GeckoLib geometry' };
});
const modelChoices = await importDialog('Automatic Import: Models', models);
if (!modelChoices) return;
for (const [index, row] of rows.entries()) {
if (modelChoices[`model_${index}`]?.path) row.resolved = { file: modelChoices[`model_${index}`].path, kind: 'Manually selected' };
if (!row.resolved) throw new Error(`Select a model for ${row.label}.`);
const override = choices[`model_${index}`];
row.resolved = override?.path ? { file: override.path, kind: 'Manually selected' }
: resolveImportModel(source, registry.packs, row.entity, row.gender, row.actor.entity_variant, io, row.slim);
if (!row.resolved) throw new Error(`No model found for ${row.label}. Select a model override for actor ${index + 1} and try again.`);
row.json = readImportJson(row.resolved.file, io);
row.geo = validateImportGeometry(row.json);
loadImportTextures(row, registry.packs, io);
@@ -5274,11 +5301,13 @@
}
const props = importPropPreviews(parsed.animationId, prepared, registry.packs.map((pack) => pack.root));
warnings.push(...props.warnings);
const review = { info: { type: 'info', text: `### Import ${parsed.animationId}\n${rows.map((row) => `${row.label}: ${row.resolved.kind}\n${row.resolved.file}`).join('\n\n')}\n\n${warnings.join('\n')}` } };
const accepted = await importDialog('Automatic Import: Review', review, 'Create Project and Import');
if (!accepted) return;
commitAutomaticImport({ parsed, rows, definition: prepared, imported, blockEntry, propEntries: props.entries });
localStorage.setItem(PACK_FOLDERS_KEY, JSON.stringify(registry.packs.map((pack) => pack.root)));
if (warnings.length) {
console.warn(`[AnimationDirector] Import notes for ${parsed.animationId}:\n${warnings.join('\n')}`);
const more = warnings.length > 1 ? ` (+${warnings.length - 1} more in Blockbench console)` : '';
Blockbench.showQuickMessage(`Imported with notes: ${warnings[0]}${more}`, 6000);
}
} catch (error) {
Blockbench.showMessageBox({ title: 'Automatically Import Animation', message: error.message, buttons: ['OK'] });
}