export const ASSET_FAMILIES = [ 'color', 'diffuse', 'roughness', 'normal', 'metalness', 'height', 'opacity', ] as const export type AssetFamily = typeof ASSET_FAMILIES[number] const ASSET_FAMILY_BY_KEY = new Map(ASSET_FAMILIES.map((family) => [family.toLowerCase(), family])) const FORBIDDEN_ASSET_FAMILY_ALIASES: ReadonlyMap = new Map([ ['basecolor', 'color'], ['base_color', 'color'], ['normalopengl', 'normal'], ['normal_opengl', 'normal'], ['metallic', 'metalness'], ['occlusionroughnessmetallic', 'roughness'], ['occlusion_roughness_metallic', 'roughness'], ]) export function getAssetFamily(value: string): AssetFamily | undefined { return ASSET_FAMILY_BY_KEY.get(value.toLowerCase()) } export function getForbiddenAssetFamilyAlias(value: string): AssetFamily | undefined { return FORBIDDEN_ASSET_FAMILY_ALIASES.get(value.toLowerCase()) } function getFileStem(filename: string) { return filename.replace(/\.[^.]+$/, '') } export function getTextureNamingError(filename: string) { const stem = getFileStem(filename) const [prefix, ...targetParts] = stem.split('_') const family = getAssetFamily(prefix) const extension = filename.split('.').pop() if (family && targetParts.every(Boolean)) return null const aliasSuggestion = getForbiddenAssetFamilyAlias(prefix) if (aliasSuggestion && targetParts.every(Boolean)) { const target = targetParts.join('_') return `Convention invalide : ${filename}. Utilisez ${aliasSuggestion}_${target}.${extension} pour cibler "${target}", ou ${aliasSuggestion}.${extension} pour tout le modele.` } const reversedParts = stem.split('_') const reversedFamily = reversedParts.length > 1 ? getAssetFamily(reversedParts[reversedParts.length - 1]) : undefined const reversedAliasSuggestion = reversedParts.length > 1 ? getForbiddenAssetFamilyAlias(reversedParts[reversedParts.length - 1]) : undefined if (reversedFamily) { const target = reversedParts.slice(0, -1).join('_') return `Convention invalide : ${filename}. Utilisez ${reversedFamily}_${target}.${extension} pour cibler "${target}", ou ${reversedFamily}.${extension} pour tout le modele.` } if (reversedAliasSuggestion) { const target = reversedParts.slice(0, -1).join('_') return `Convention invalide : ${filename}. Utilisez ${reversedAliasSuggestion}_${target}.${extension} pour cibler "${target}", ou ${reversedAliasSuggestion}.${extension} pour tout le modele.` } return `Asset inconnu : ${filename}. Familles autorisees : ${formatAssetFamilies()}. Utilisez asset.png pour tout le modele ou asset_objet.png pour cibler un objet.` } export function formatAssetFamilies() { return ASSET_FAMILIES.join(', ') }