Files
gtav-src/tools_ng/bin/audio/FaceFx 2012/Scripts/FxAnimation.py
T
2025-09-29 00:52:08 +02:00

610 lines
26 KiB
Python
Executable File

""" This module provides Python wrappers around animation code.
classes:
Key -- A single key in an animation curve.
Curve -- A collection of keys that can be evaluated at a certain time.
ChildEvent -- A single event in the event template.
ChildEventGroup -- A group of child events, from which only one can spawn.
EventTemplate -- The collection of child event groups which produces the Take.
Animation -- A collection of curves and an event template. A wrapper around a
FaceFx Studio animation.
PreviewAnimationSettings - The preview animation settings for the currently
selected animation in FaceFX Studio.
Owner: Jamie Redmond
Copyright (c) 2002-2011 OC3 Entertainment, Inc.
"""
from FxStudio import getEventTemplate, getEventTake, getAnimationProperties,\
getCurveNames, getAnimationNames, getKeys, isCurveOwnedByAnalysis,\
issueCommand, getPreviewAnimationSettings, FaceFXError
from FxPhonemes import PhonemeWordList
class Key(object):
""" A wrapper around a single key.
instance variables:
time -- The time of the key, in seconds.
value -- The value of the key
slopeIn -- The slope of the curve as it comes into the key
slopeOut -- The slope of the curve as it leaves the key
"""
def __init__(self, keyTupleFromStudio):
""" Initializes the key from a tuple sent back from FaceFx Studio.
parameters:
keyTupleFromStudio -- A tuple (time, value, slopeIn, slopeOut)
"""
self.time = keyTupleFromStudio[0]
self.value = keyTupleFromStudio[1]
self.slopeIn = keyTupleFromStudio[2]
self.slopeOut = keyTupleFromStudio[3]
def __str__(self):
""" Returns the string representation of the key. """
return 'Key: time={0}, value={1}, slopeIn={2}, slopeOut={3}'.format(
self.time, self.value, self.slopeIn, self.slopeOut)
def __repr__(self):
""" Returns the Python represenation of the key. """
return 'Key(({0}, {1}, {2}, {3}))'.format(self.time, self.value,
self.slopeIn, self.slopeOut)
class HermiteKeyInterpolator(object):
""" A class that performs a modified Hermite interpolation between two keys.
"""
def interpolate(self, firstKey, secondKey, time):
""" Perform a modified Hermite interpolation between two keys.
Returns the curve value at the requested time between the keys.
keyword arguments:
firstKey -- the key with a time less than "time"
secondKey -- the key with a time greater than "time"
time -- the time for which to evaluate.
returns: float
"""
time1 = firstKey.time
time2 = secondKey.time
deltaTime = time2 - time1
parametricTime = (time - time1) / deltaTime
p0 = firstKey.value
p1 = secondKey.value
m0 = firstKey.slopeOut * deltaTime
m1 = secondKey.slopeIn * deltaTime
return parametricTime * (parametricTime * (parametricTime *
(2.0*p0 - 2.0*p1 + m0 + m1) + (-3.0*p0 + 3.0*p1 - 2.0*m0 - m1)) +
m0) + p0
class Curve(object):
""" A collection of keys that can be evaluated at a given time.
instance variables:
animation -- a reference back to the animation containing this curve
name -- the name of the curve
interpolator -- an object that can interpolate(firstKey, secondKey, time)
keys -- a list of the keys in the animation
isOwnedByAnalysis -- boolean; True if the curve is owned by analysis,
meaning changes cannot be brought back into FaceFX Studio
"""
def __init__(self, name, curveTupleFromStudio, animation):
""" Initializes the animation with the tuple from studio. """
self.animation = animation
self.name = name
self.interpolator = HermiteKeyInterpolator()
self.keys = [Key(key) for key in curveTupleFromStudio]
self.isOwnedByAnalysis = isCurveOwnedByAnalysis(
self.animation.groupName, self.animation.name, self.name)
def __str__(self):
""" Returns the string representation of the curve. """
return 'Curve: "{0}" [{1} keys, owned by {2}]'.format(
self.name, len(self),
'Analysis' if self.isOwnedByAnalysis else 'User')
def __repr__(self):
""" Hackish repr to make printing lists of curves pretty. """
return self.__str__()
def __len__(self):
""" Returns the number of keys in the curve. """
return len(self.keys)
def __getitem__(self, item):
""" Returns the key at item. """
return self.keys[item]
def getInterpolator(self):
""" Returns the interpolator object in use by the curve. """
return self.interpolator
def setInterpolator(self, interpolator):
""" Sets the interpolator object that the curve will use to evaluate """
self.interpolator = interpolator
def getNumKeys(self):
""" Returns the number of keys in the curve """
return len(self)
def getStartTime(self):
""" Returns the time of the first key. """
try:
return self[0].time
except IndexError:
return 0.0
def getEndTime(self):
""" Returns the time of the last key. """
try:
return self[-1].time
except IndexError:
return 0.0
def evaluateAt(self, time):
""" Evaluates the curve at the given time.
keyword arguments:
time -- the time in seconds to evaluate at
"""
value = 0.0
numKeys = self.getNumKeys()
if( numKeys > 0 ):
numKeysM1 = numKeys - 1
# Check for out-of-range time and clamp to end points of curve.
if time <= self.keys[0].time:
value = self.keys[0].value
elif time >= self.keys[numKeysM1].time:
value = self.keys[numKeysM1].value
else:
# The time is in range.
if 1 == numKeys:
value = self.keys[0].value
else:
# Find the bounding keys.
firstKey = 0
secondKey = 0
pos = 0
for i in range(numKeysM1):
if self.keys[i].time <= time and time < self.keys[i+1].time:
pos = i
break
if pos != numKeysM1:
firstKey = pos
secondKey = pos+1
else:
firstKey = pos-1
secondKey = pos
# Interpolate.
value = self.interpolator.interpolate(self.keys[firstKey], self.keys[secondKey], time)
return value
class ChildEvent(object):
""" A wrapper around a child event in the event template.
instance variables:
animGroupName -- the name of the animation group the event points to
animName -- the name of the animation the event points to
startTimeRange -- a tuple defining the range when the event can start
magnitudeRange -- a tuple defining the range of the event's magnitude scale
durationRange -- a tuple defining the range of the event's duration scale
blendInRange -- a tuple defining the range of the event's blend in time
blendOutRange -- a tuple defining the range of the event's blend out time
customPayload -- a string that will be sent back to the game engine at the
event's ingress when the event is played in game
eventID -- a read-only internal identifier
isDurationScaledByParent -- If this is true then when the take is created
this event's duration will be scaled by the duration scale of the
event that spawned it into the take. If this is false or this event
was not spawned by another event this event's duration will fall within
its own durationRange.
isMagnitudeScaledByParent -- If this is true then when the take is created
this event's magnitude will be scaled by the magnitude scale of the
event that spawned it into the take. If this is false or this event
was not spawned by another event this event's magnitude will fall within
its own magnitudeRange.
isBlendUnscaled -- True if the event's blend times are unscaled by the
event's duration
useParentBlendTimes -- True if the event's blend times where inherited from
the event that spawned it into the take.
shouldPersistValue -- True if the event's values will "stick" on the
character at the event's egress
spawnConditionProbability -- The probability that the event will be spawned
into the take. A value of None or 1.0 means it is always spawned.
spawnConditionDurationScale -- If the duration scale of the event that
could possibly spawn this event into the take falls into this range then
this event will be spawned. A value of None means it is unbounded to
that side.
spawnConditionMagnitudeScale -- If the magnitude scale of the event that
could possibly spawn this event into the take falls into this range then
this event will be spawned. A value of None means it is unbounded to
that side.
spawnConditionStartTimeOffset -- If the actual start time of the event that
could possibly spawn this event into the take falls into this range this
event will be spawned. A value of None means it is unbounded to
that side.
weight -- The weight assigned to this event in a group, used for picking
one event from a group of child events
"""
def __init__(self, childEventTupleFromStudio):
""" Initializes the child event with the tuple from Studio. """
self.animGroupName = childEventTupleFromStudio[0]
self.animName = childEventTupleFromStudio[1]
self.startTimeRange = childEventTupleFromStudio[2]
self.magnitudeRange = childEventTupleFromStudio[3]
self.durationRange = childEventTupleFromStudio[4]
self.blendInRange = childEventTupleFromStudio[5]
self.blendOutRange = childEventTupleFromStudio[6]
self.customPayload = childEventTupleFromStudio[7]
self.eventID = childEventTupleFromStudio[8]
self.isDurationScaledByParent = childEventTupleFromStudio[9]
self.isMagnitudeScaledByParent = childEventTupleFromStudio[10]
self.isBlendUnscaled = childEventTupleFromStudio[11]
self.useParentBlendTimes = childEventTupleFromStudio[12]
self.shouldPersistValues = childEventTupleFromStudio[13]
self.spawnConditionProbability = childEventTupleFromStudio[14]
self.spawnConditionDurationScale = childEventTupleFromStudio[15]
self.spawnConditionMagnitudeScale = childEventTupleFromStudio[16]
self.spawnConditionStartTimeOffset = childEventTupleFromStudio[17]
self.weight = childEventTupleFromStudio[18]
def __str__(self):
""" Returns the string representation of the child event. """
r = "animGroupName: " + str(self.animGroupName) + "\n"
r += "animName: " + str(self.animName) + "\n"
r += "startTimeRange: (" + str(self.startTimeRange[0]) + ", " + str(self.startTimeRange[1]) + ")" + "\n"
r += "magnitudeRange: (" + str(self.magnitudeRange[0]) + ", " + str(self.magnitudeRange[1]) + ")" + "\n"
r += "durationRange: (" + str(self.durationRange[0]) + ", " + str(self.durationRange[1]) + ")" + "\n"
r += "blendInRange: (" + str(self.blendInRange[0]) + ", " + str(self.blendInRange[1]) + ")" + "\n"
r += "blendOutRange: (" + str(self.blendOutRange[0]) + ", " + str(self.blendOutRange[1]) + ")" + "\n"
r += "customPayload: " + str(self.customPayload) + "\n"
r += "eventID: " + str(self.eventID) + "\n"
r += "isDurationScaledByParent: " + str(self.isDurationScaledByParent) + "\n"
r += "isMagnitudeScaledByParent: " + str(self.isMagnitudeScaledByParent) + "\n"
r += "isBlendUnscaled: " + str(self.isBlendUnscaled) + "\n"
r += "useParentBlendTimes: " + str(self.useParentBlendTimes) + "\n"
r += "shouldPersistValues: " + str(self.shouldPersistValues) + "\n"
r += "spawnConditionProbability: " + str(self.spawnConditionProbability) + "\n"
r += "spawnConditionDurationScale: (" + str(self.spawnConditionDurationScale[0]) + ", " + str(self.spawnConditionDurationScale[1]) + ")" + "\n"
r += "spawnConditionMagnitudeScale: (" + str(self.spawnConditionMagnitudeScale[0]) + ", " + str(self.spawnConditionMagnitudeScale[1]) + ")" + "\n"
r += "spawnConditionStartTimeOffset: (" + str(self.spawnConditionStartTimeOffset[0]) + ", " + str(self.spawnConditionStartTimeOffset[1]) + ")" + "\n"
r += "weight: " + str(self.weight) + "\n"
return r
class ChildEventGroup(object):
""" A group of child events.
instance variables:
childEvents -- a list of the child events in the group
"""
def __init__(self, childEventGroupTupleFromStudio):
""" Initializes the child event group with the tuple from Studio. """
self.childEvents = [ChildEvent(e) for e in
childEventGroupTupleFromStudio]
def __len__(self):
""" Returns the number of child events in the group. """
return len(self.childEvents)
def __getitem__(self, item):
""" Returns the child event at item """
return self.childEvents[item]
def getNumChildEvents(self):
""" Returns the number of child events in the group. """
return len(self)
def __str__(self):
""" Returns the string representation of the child event group. """
r = str(self.getNumChildEvents()) + " childEvents:\n"
childEventIndex = 0
for childEvent in self.childEvents:
r += "childEvent " + str(childEventIndex) + ":\n"
r += str(childEvent)
childEventIndex += 1
return r
class EventTemplate(object):
""" An event template defines the events that might be spawned in a take.
instance variables:
templateRevisionID -- a read-only internal identifier
childEventGroups -- a list of the groups of child events in the template
"""
def __init__(self, animGroupName, animName):
""" Requests the event template for the given animation from Studio. """
eventTemplateTuple = getEventTemplate(animGroupName, animName)
self.templateRevisionID = -1
self.childEventGroups = []
if len(eventTemplateTuple) >= 2:
self.templateRevisionID = eventTemplateTuple[0]
for childEventGroup in eventTemplateTuple[1]:
self.childEventGroups.append(ChildEventGroup(childEventGroup))
def __len__(self):
""" Returns the number of child event groups in the template. """
return len(self.childEventGroups)
def __getitem__(self, item):
""" Returns the child event group at item. """
return self.childEventGroups[item]
def getNumChildEventGroups(self):
""" Returns the number of child event groups in the template. """
return len(self.childEventGroups)
def __str__(self):
""" Returns the string representation of the event template. """
r = "templateRevisionID: " + str(self.templateRevisionID) + "\n"
r += str(self.getNumChildEventGroups()) + " childEventGroups:\n"
childEventGroupIndex = 0
for childEventGroup in self.childEventGroups:
r += "childEventGroup " + str(childEventGroupIndex) + ":\n"
r += str(childEventGroup)
childEventGroupIndex += 1
return r
class Event(object):
""" An event contained in a take.
instance variables:
animGroupName -- the name of the animation group the event points to
animName -- the name of the animation the event points to
startTime -- the start time of the event (in seconds)
duration -- the duration of the event (in seconds)
durationScale -- the duration scale of the event
magnitudeScale -- the magnitude scale of the event
blendInTime -- the blend in time of the event (in seconds)
blendOutTime -- the blend out time of the event (in seconds)
shouldPersistValue -- True if the event's values will "stick" on the
character at the event's egress
customPayload -- a string that will be sent back to the game engine at the
event's ingress when the event is played in game
eventID -- a read-only internal identifier
"""
def __init__(self, eventTupleFromStudio):
""" Initializes the event with the tuple from Studio. """
self.animGroupName = eventTupleFromStudio[0]
self.animName = eventTupleFromStudio[1]
self.startTime = eventTupleFromStudio[2]
self.duration = eventTupleFromStudio[3]
self.durationScale = eventTupleFromStudio[4]
self.magnitudeScale = eventTupleFromStudio[5]
self.blendInTime = eventTupleFromStudio[6]
self.blendOutTime = eventTupleFromStudio[7]
self.shouldPersistValues = eventTupleFromStudio[8]
self.customPayload = eventTupleFromStudio[9]
self.eventID = eventTupleFromStudio[10]
def __str__(self):
""" Returns the string representation of the event. """
r = "animGroupName: " + str(self.animGroupName) + "\n"
r += "animName: " + str(self.animName) + "\n"
r += "startTime: " + str(self.startTime) + "\n"
r += "duration: " + str(self.duration) + "\n"
r += "durationScale: " + str(self.durationScale) + "\n"
r += "magnitudeScale: " + str(self.magnitudeScale) + "\n"
r += "blendInTime: " + str(self.blendInTime) + "\n"
r += "blendOutTime: " + str(self.blendOutTime) + "\n"
r += "shouldPersistValues: " + str(self.shouldPersistValues) + "\n"
r += "customPayload: " + str(self.customPayload) + "\n"
r += "eventID: " + str(self.eventID) + "\n"
return r
class EventTake(object):
""" An event take defines the events that are actually in the take.
instance variables:
events -- a list of the events contained in the take
"""
def __init__(self, animGroupName, animName):
""" Requests the event take for the given animation from Studio. """
eventTakeTuple = getEventTake(animGroupName, animName)
self.events = []
for event in eventTakeTuple:
self.events.append(Event(event))
def __len__(self):
""" Returns the number of events in the take. """
return len(self.events)
def __getitem__(self, item):
""" Returns the event at item. """
return self.events[item]
def getNumEvents(self):
""" Returns the number of events in the take. """
return len(self.events)
def __str__(self):
""" Returns the string representation of the event take. """
r = str(self.getNumEvents()) + " events:\n"
eventIndex = 0
for event in self.events:
r += "event " + str(eventIndex) + ":\n"
r += str(event)
eventIndex += 1
return r
class Animation(object):
""" A collection of curves, an event template, an event take, and other
animation properties.
instance variables:
groupName -- the name of the group containing the animation
name -- the name of the animation
startTime -- the start time of the animation, either events or curves
endTime -- the end time of the animation, either events or curves
curvesStartTime -- the start time of the curves
curvesEndTime -- the end time of the curves
frameRate -- the frame rate of the animation
absoluteAudioAssetPath -- the absolute path to the audio asset
audioAssetPath -- the relative path to the audio asset
language -- the language the animation was analyzed in
analysisActor -- the name of the analysis actor used to analyze the anim
analysisText -- the text used to analyze the anim
phonemeWordList -- a PhonemeWordList object containing the phonemes and
words that were output by the analysis
curves -- a list of Curve objects containing the curves in the animation
eventTemplate -- an EventTemplate object containing the child event groups
eventTake -- an EventTake object containing the events
"""
def __init__(self, animGroupName, animName):
""" Initializes the animation by pulling the data from Studio. """
self.groupName = animGroupName
self.name = animName
self.path = animGroupName + "/" + animName
try:
animationProperties = getAnimationProperties(animGroupName, animName)
self.startTime = animationProperties[0]
self.endTime = animationProperties[1]
self.curvesStartTime = animationProperties[2]
self.curvesEndTime = animationProperties[3]
self.frameRate = animationProperties[4]
self.absoluteAudioAssetPath = animationProperties[5]
self.audioAssetPath = animationProperties[6]
self.language = animationProperties[7]
self.analysisActor = animationProperties[8]
self.analysisText = animationProperties[9]
self.phonemeWordList = PhonemeWordList(animGroupName, animName)
curveNames = getCurveNames(animGroupName, animName)
self.curves = [Curve(c, getKeys(animGroupName, animName, c), self)
for c in curveNames]
self.eventTemplate = EventTemplate(animGroupName, animName)
self.eventTake = EventTake(animGroupName, animName)
except Exception, e:
raise FaceFXError('{0}'.format(e))
def getNumCurves(self):
""" Returns the number of curves in the animation. """
return len(self.curves)
def findCurve(self, curveName):
""" Returns the curve with the requested name, or None. """
for curve in self.curves:
if curve.name == curveName:
return curve
return None
def __str__(self):
""" Returns the string representation of the Animation. """
r = str(self.path) + ":\n"
r += " startTime: " + str(self.startTime) + "\n"
r += " endTime: " + str(self.endTime) + "\n"
r += " curvesStartTime: " + str(self.curvesStartTime) + "\n"
r += " curvesEndTime: " + str(self.curvesEndTime) + "\n"
r += " frameRate: " + str(self.frameRate) + "\n"
r += " absoluteAudioAssetPath: " + self.absoluteAudioAssetPath + "\n"
r += " audioAssetPath: " + self.audioAssetPath + "\n"
r += " language: " + self.language + "\n"
r += " analysisActor: " + self.analysisActor + "\n"
r += " analysisText: " + self.analysisText + "\n"
r += " " + str(self.getNumCurves()) + " curves:\n"
curveIndex = 0
for curve in self.curves:
r += " [" + str(curveIndex) + "] " + str(curve) + "\n"
curveIndex += 1
return r
class PreviewAnimationSettings(object):
""" The preview animation settings for the currently selected animation in
FaceFX Studio.
instance variables:
blendMode -- the current preview animation blend mode
animationName -- the name of the skeletal animation being used for preview
purposes
length -- the length of the preview animation (in seconds)
startTime -- the time at which the preview animation starts in the FaceFX
Studio timeline (in seconds)
blendInTime -- the time (in seconds) over which the preview animation blends
in
blendOutTime -- the time (in seconds) over which the preview animation
blends out
loop - inidcates whether or not the preview animation is looping
Notes: blendMode is always valid (a string), but the other instance
variables will be set to None if there is no preview animation
selected in FaceFX Studio
"""
def __init__(self):
""" Initializes the preview animation settings with the tuple from Studio. """
previewAnimationSettingsTupleFromStudio = getPreviewAnimationSettings()
self.blendMode = previewAnimationSettingsTupleFromStudio[0]
if "" == previewAnimationSettingsTupleFromStudio[1]:
self.animationName = None
self.length = None
self.startTime = None
self.blendInTime = None
self.blendOutTime = None
self.loop = None
else:
self.animationName = previewAnimationSettingsTupleFromStudio[1]
self.length = previewAnimationSettingsTupleFromStudio[2]
self.startTime = previewAnimationSettingsTupleFromStudio[3]
self.blendInTime = previewAnimationSettingsTupleFromStudio[4]
self.blendOutTime = previewAnimationSettingsTupleFromStudio[5]
self.loop = previewAnimationSettingsTupleFromStudio[6]
def __str__(self):
""" Returns the string representation of the preview animation settings. """
r = "Preview Animation Settings:\n"
r += " blendMode: " + str(self.blendMode) + "\n"
r += " animationName: " + str(self.animationName) + "\n"
r += " length: " + str(self.length) + "\n"
r += " startTime: " + str(self.startTime) + "\n"
r += " blendInTime: " + str(self.blendInTime) + "\n"
r += " blendOutTime: " + str(self.blendOutTime) + "\n"
r += " loop: " + str(self.loop) + "\n"
return r