Some checks failed
CI/CD Pipeline / Test & Lint (16.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (18.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (20.x) (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Release (push) Has been cancelled
Release / Validate Version (push) Has been cancelled
Release / Build and Test (push) Has been cancelled
Release / Create Release (push) Has been cancelled
Release / Publish to NPM (push) Has been cancelled
Release / Deploy Demo (push) Has been cancelled
Animation Processing Pipeline / Validate Animation Names (push) Has been cancelled
Animation Processing Pipeline / Process Blender Animation Assets (push) Has been cancelled
Animation Processing Pipeline / Update Animation Documentation (push) Has been cancelled
Animation Processing Pipeline / Deploy Animation Demo (push) Has been cancelled
- Added AnimationNameMapper class to handle conversion between different animation naming schemes (legacy, artist, hierarchical, semantic). - Included methods for initialization, pattern matching, conversion, and validation of animation names. - Developed comprehensive unit tests for the animation name converter and demo pages using Playwright. - Created a Vite configuration for the demo application, including asset handling and optimization settings. - Enhanced the demo with features for batch conversion, performance metrics, and responsive design.
434 lines
12 KiB
JavaScript
434 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Convert Animation Names Script
|
||
* Converts animation names between different schemes
|
||
*/
|
||
|
||
import fs from 'fs'
|
||
import path from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
|
||
const __filename = fileURLToPath(import.meta.url)
|
||
const __dirname = path.dirname(__filename)
|
||
|
||
const PROJECT_ROOT = path.resolve(__dirname, '..')
|
||
const ANIMATION_MAPPER_PATH = path.join(PROJECT_ROOT, 'src', 'animation', 'AnimationNameMapper.js')
|
||
|
||
/**
|
||
* Convert animation names based on command line arguments or file input
|
||
*/
|
||
async function convertAnimationNames () {
|
||
try {
|
||
const args = process.argv.slice(2)
|
||
|
||
// Show help if no arguments provided
|
||
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
||
showHelp()
|
||
return
|
||
}
|
||
|
||
console.log('🔄 Converting Animation Names...')
|
||
|
||
// Import the AnimationNameMapper
|
||
const { AnimationNameMapper } = await import(ANIMATION_MAPPER_PATH)
|
||
const mapper = new AnimationNameMapper()
|
||
|
||
const options = parseArguments(args)
|
||
|
||
if (options.inputFile) {
|
||
await convertFromFile(mapper, options)
|
||
} else if (options.animationName) {
|
||
await convertSingle(mapper, options)
|
||
} else if (options.batchConvert) {
|
||
await convertBatch(mapper, options)
|
||
} else {
|
||
console.error('❌ No input provided. Use --help for usage information.')
|
||
process.exit(1)
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error converting animation names:', error.message)
|
||
process.exit(1)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Parse command line arguments
|
||
*/
|
||
function parseArguments (args) {
|
||
const options = {
|
||
animationName: null,
|
||
fromScheme: null,
|
||
toScheme: null,
|
||
inputFile: null,
|
||
outputFile: null,
|
||
batchConvert: false,
|
||
allSchemes: false,
|
||
validate: false
|
||
}
|
||
|
||
for (let i = 0; i < args.length; i++) {
|
||
const arg = args[i]
|
||
const nextArg = args[i + 1]
|
||
|
||
switch (arg) {
|
||
case '--name':
|
||
case '-n':
|
||
options.animationName = nextArg
|
||
i++
|
||
break
|
||
case '--from':
|
||
case '-f':
|
||
options.fromScheme = nextArg
|
||
i++
|
||
break
|
||
case '--to':
|
||
case '-t':
|
||
options.toScheme = nextArg
|
||
i++
|
||
break
|
||
case '--input':
|
||
case '-i':
|
||
options.inputFile = nextArg
|
||
i++
|
||
break
|
||
case '--output':
|
||
case '-o':
|
||
options.outputFile = nextArg
|
||
i++
|
||
break
|
||
case '--batch':
|
||
case '-b':
|
||
options.batchConvert = true
|
||
break
|
||
case '--all-schemes':
|
||
case '-a':
|
||
options.allSchemes = true
|
||
break
|
||
case '--validate':
|
||
case '-v':
|
||
options.validate = true
|
||
break
|
||
}
|
||
}
|
||
|
||
return options
|
||
}
|
||
|
||
/**
|
||
* Convert a single animation name
|
||
*/
|
||
async function convertSingle (mapper, options) {
|
||
const { animationName, fromScheme, toScheme, allSchemes, validate } = options
|
||
|
||
console.log(`\n🎯 Converting: ${animationName}`)
|
||
|
||
if (validate) {
|
||
const validation = mapper.validateAnimationName(animationName)
|
||
console.log('\n✅ Validation Result:')
|
||
console.log(`Valid: ${validation.isValid}`)
|
||
if (validation.detectedScheme) {
|
||
console.log(`Detected Scheme: ${validation.detectedScheme}`)
|
||
}
|
||
if (validation.suggestions?.length > 0) {
|
||
console.log(`Suggestions: ${validation.suggestions.join(', ')}`)
|
||
}
|
||
if (validation.errors?.length > 0) {
|
||
console.log(`Errors: ${validation.errors.join(', ')}`)
|
||
}
|
||
}
|
||
|
||
if (allSchemes) {
|
||
// Convert to all schemes
|
||
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic']
|
||
console.log('\n🔄 Converting to all schemes:')
|
||
|
||
schemes.forEach(scheme => {
|
||
try {
|
||
const converted = mapper.convert(animationName, scheme)
|
||
console.log(`${scheme.padEnd(12)}: ${converted}`)
|
||
} catch (error) {
|
||
console.log(`${scheme.padEnd(12)}: ❌ ${error.message}`)
|
||
}
|
||
})
|
||
} else if (toScheme) {
|
||
// Convert to specific scheme
|
||
try {
|
||
const converted = mapper.convert(animationName, toScheme)
|
||
console.log(`\n🎯 Result: ${converted}`)
|
||
|
||
if (fromScheme) {
|
||
console.log(`From: ${fromScheme} -> To: ${toScheme}`)
|
||
} else {
|
||
const detectedScheme = mapper.detectScheme(animationName)
|
||
console.log(`From: ${detectedScheme} -> To: ${toScheme}`)
|
||
}
|
||
} catch (error) {
|
||
console.error(`❌ Conversion failed: ${error.message}`)
|
||
process.exit(1)
|
||
}
|
||
} else {
|
||
console.log('ℹ️ No target scheme specified. Use --to <scheme> or --all-schemes')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Convert animation names from a file
|
||
*/
|
||
async function convertFromFile (mapper, options) {
|
||
const { inputFile, outputFile, toScheme, allSchemes } = options
|
||
|
||
if (!fs.existsSync(inputFile)) {
|
||
throw new Error(`Input file not found: ${inputFile}`)
|
||
}
|
||
|
||
console.log(`📁 Reading from file: ${inputFile}`)
|
||
|
||
const content = fs.readFileSync(inputFile, 'utf8')
|
||
let animationNames = []
|
||
|
||
try {
|
||
// Try to parse as JSON first
|
||
const parsed = JSON.parse(content)
|
||
if (Array.isArray(parsed)) {
|
||
animationNames = parsed
|
||
} else if (parsed.animations && Array.isArray(parsed.animations)) {
|
||
animationNames = parsed.animations
|
||
} else {
|
||
animationNames = Object.values(parsed).filter(v => typeof v === 'string')
|
||
}
|
||
} catch {
|
||
// If not JSON, treat as line-separated text
|
||
animationNames = content.split('\n')
|
||
.map(line => line.trim())
|
||
.filter(line => line && !line.startsWith('#'))
|
||
}
|
||
|
||
console.log(`📋 Found ${animationNames.length} animation names to convert`)
|
||
|
||
const results = {
|
||
timestamp: new Date().toISOString(),
|
||
inputFile,
|
||
totalAnimations: animationNames.length,
|
||
conversions: [],
|
||
errors: []
|
||
}
|
||
|
||
if (allSchemes) {
|
||
// Convert each animation to all schemes
|
||
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic']
|
||
|
||
animationNames.forEach(animName => {
|
||
const animResult = {
|
||
original: animName,
|
||
conversions: {}
|
||
}
|
||
|
||
schemes.forEach(scheme => {
|
||
try {
|
||
animResult.conversions[scheme] = mapper.convert(animName, scheme)
|
||
} catch (error) {
|
||
animResult.conversions[scheme] = { error: error.message }
|
||
results.errors.push(`${animName} -> ${scheme}: ${error.message}`)
|
||
}
|
||
})
|
||
|
||
results.conversions.push(animResult)
|
||
})
|
||
} else if (toScheme) {
|
||
// Convert to specific scheme
|
||
animationNames.forEach(animName => {
|
||
const animResult = {
|
||
original: animName,
|
||
target: toScheme
|
||
}
|
||
|
||
try {
|
||
animResult.converted = mapper.convert(animName, toScheme)
|
||
animResult.fromScheme = mapper.detectScheme(animName)
|
||
} catch (error) {
|
||
animResult.error = error.message
|
||
results.errors.push(`${animName}: ${error.message}`)
|
||
}
|
||
|
||
results.conversions.push(animResult)
|
||
})
|
||
}
|
||
|
||
// Save results
|
||
if (outputFile) {
|
||
fs.writeFileSync(outputFile, JSON.stringify(results, null, 2), 'utf8')
|
||
console.log(`📄 Results saved to: ${outputFile}`)
|
||
} else {
|
||
// Print to console
|
||
console.log('\n📊 Conversion Results:')
|
||
results.conversions.forEach((result, index) => {
|
||
console.log(`\n${index + 1}. ${result.original}`)
|
||
if (result.conversions) {
|
||
Object.entries(result.conversions).forEach(([scheme, value]) => {
|
||
if (typeof value === 'string') {
|
||
console.log(` ${scheme}: ${value}`)
|
||
} else {
|
||
console.log(` ${scheme}: ❌ ${value.error}`)
|
||
}
|
||
})
|
||
} else if (result.converted) {
|
||
console.log(` ${result.target}: ${result.converted}`)
|
||
} else if (result.error) {
|
||
console.log(` ❌ ${result.error}`)
|
||
}
|
||
})
|
||
}
|
||
|
||
if (results.errors.length > 0) {
|
||
console.log(`\n⚠️ ${results.errors.length} errors encountered`)
|
||
if (results.errors.length <= 5) {
|
||
results.errors.forEach(error => console.log(` • ${error}`))
|
||
}
|
||
}
|
||
|
||
console.log(`\n✅ Processed ${results.totalAnimations} animations`)
|
||
}
|
||
|
||
/**
|
||
* Batch convert all animations in the current mapper
|
||
*/
|
||
async function convertBatch (mapper, options) {
|
||
const { outputFile } = options
|
||
|
||
console.log('🔄 Batch converting all animations in the system...')
|
||
|
||
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic']
|
||
const allResults = {
|
||
timestamp: new Date().toISOString(),
|
||
totalAnimations: 0,
|
||
schemeStats: {},
|
||
conversionMatrix: {}
|
||
}
|
||
|
||
schemes.forEach(fromScheme => {
|
||
const animations = mapper.getAllAnimationsByScheme(fromScheme)
|
||
allResults.schemeStats[fromScheme] = animations.length
|
||
allResults.totalAnimations += animations.length
|
||
|
||
if (!allResults.conversionMatrix[fromScheme]) {
|
||
allResults.conversionMatrix[fromScheme] = {}
|
||
}
|
||
|
||
schemes.forEach(targetScheme => {
|
||
const conversions = []
|
||
let errors = 0
|
||
|
||
animations.forEach(anim => {
|
||
try {
|
||
const converted = mapper.convert(anim, targetScheme)
|
||
conversions.push({ original: anim, converted })
|
||
} catch (error) {
|
||
conversions.push({ original: anim, error: error.message })
|
||
errors++
|
||
}
|
||
})
|
||
|
||
allResults.conversionMatrix[fromScheme][targetScheme] = {
|
||
total: animations.length,
|
||
successful: animations.length - errors,
|
||
errors,
|
||
successRate: Math.round(((animations.length - errors) / animations.length) * 100),
|
||
conversions: conversions.slice(0, 10) // Include sample conversions
|
||
}
|
||
})
|
||
})
|
||
|
||
// Print summary
|
||
console.log('\n📊 Batch Conversion Summary:')
|
||
console.log(`Total animations: ${allResults.totalAnimations}`)
|
||
console.log('\nBy scheme:')
|
||
Object.entries(allResults.schemeStats).forEach(([scheme, count]) => {
|
||
console.log(` ${scheme}: ${count} animations`)
|
||
})
|
||
|
||
console.log('\nConversion matrix (success rates):')
|
||
schemes.forEach(fromScheme => {
|
||
console.log(`\n${fromScheme}:`)
|
||
schemes.forEach(toScheme => {
|
||
const result = allResults.conversionMatrix[fromScheme][toScheme]
|
||
console.log(` -> ${toScheme}: ${result.successRate}% (${result.successful}/${result.total})`)
|
||
})
|
||
})
|
||
|
||
// Save results
|
||
if (outputFile) {
|
||
fs.writeFileSync(outputFile, JSON.stringify(allResults, null, 2), 'utf8')
|
||
console.log(`\n📄 Full results saved to: ${outputFile}`)
|
||
} else {
|
||
// Save to default location
|
||
const reportsDir = path.join(PROJECT_ROOT, 'reports')
|
||
if (!fs.existsSync(reportsDir)) {
|
||
fs.mkdirSync(reportsDir, { recursive: true })
|
||
}
|
||
const defaultFile = path.join(reportsDir, 'batch-conversion-results.json')
|
||
fs.writeFileSync(defaultFile, JSON.stringify(allResults, null, 2), 'utf8')
|
||
console.log(`\n📄 Results saved to: ${defaultFile}`)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Show help information
|
||
*/
|
||
function showHelp () {
|
||
console.log(`
|
||
🎬 Animation Name Converter
|
||
|
||
Convert animation names between different naming schemes in the Owen Animation System.
|
||
|
||
USAGE:
|
||
node convert-animation-names.js [OPTIONS]
|
||
|
||
SINGLE CONVERSION:
|
||
--name, -n <name> Animation name to convert
|
||
--to, -t <scheme> Target scheme (legacy|artist|hierarchical|semantic)
|
||
--all-schemes, -a Convert to all schemes
|
||
--validate, -v Validate the animation name
|
||
|
||
FILE CONVERSION:
|
||
--input, -i <file> Input file with animation names (JSON or line-separated)
|
||
--output, -o <file> Output file for results (optional)
|
||
--to, -t <scheme> Target scheme for conversion
|
||
|
||
BATCH OPERATIONS:
|
||
--batch, -b Convert all animations in the system
|
||
--output, -o <file> Output file for batch results
|
||
|
||
EXAMPLES:
|
||
# Convert single animation to semantic scheme
|
||
node convert-animation-names.js --name wait_idle_L --to semantic
|
||
|
||
# Convert to all schemes
|
||
node convert-animation-names.js --name Owen_ReactAngry --all-schemes
|
||
|
||
# Validate an animation name
|
||
node convert-animation-names.js --name unknown_animation --validate
|
||
|
||
# Convert from file
|
||
node convert-animation-names.js --input animations.json --to artist --output results.json
|
||
|
||
# Batch convert all animations
|
||
node convert-animation-names.js --batch --output full-conversion-matrix.json
|
||
|
||
SCHEMES:
|
||
legacy - e.g., wait_idle_L, react_angry_S
|
||
artist - e.g., Owen_WaitIdle, Owen_ReactAngry
|
||
hierarchical - e.g., owen.state.wait.idle.loop
|
||
semantic - e.g., OwenWaitIdleLoop, OwenReactAngryShort
|
||
`)
|
||
}
|
||
|
||
// Run the script if called directly
|
||
if (process.argv[1] === __filename) {
|
||
convertAnimationNames()
|
||
.catch(error => {
|
||
console.error('💥 Script failed:', error)
|
||
process.exit(1)
|
||
})
|
||
}
|