1
$\begingroup$

I am trying to create a data driven animation face animation using a socket and Modal operator. In each loop of the Modal operator I receive data from a socket , use the data to create the keyframes of the animation and then play the animation. But i am unable to figure out how to stop the animation when the animation has reached the end keyframe.

Here is my code

SocketModalOperator.py

            import bpy
            import socket
            import os
            import sys

            absolutePath = "/Users/pulkitjuneja/Documents/projects/EVA/scripts"
            sys.path.append(absolutePath)

            from animationController import main


            class socketModal(bpy.types.Operator):
                bl_idname = "object.modal_operator"
                bl_label = "Lip synch Operator"
                _timer = None

                def __init__(self):
                    print("Listen Start")
                    os.chdir(absolutePath)
                    self.port = 1301

                def __del__(self):
                    print("Listen End")

                def execute(self, context):
                    print ("execute")
                    self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                    self.socket.setblocking(0)
                    self.socket.bind(("127.0.0.1", self.port))
                    print('socket listening on port', self.port)
                    self._timer = context.window_manager.event_timer_add(1.0, context.window)
                    context.window_manager.modal_handler_add(self)
                    return {'RUNNING_MODAL'}

                def modal(self, context, event):
                    print ("modal")
                    if event.type == "TIMER":
                        data = None
                        try:
                            data = self.socket.recv(1024 * 1024)
                        except socket.error:
                            print ("no data") 
                    main(context, data) # call to animation controller
                    if event.type == 'BACK_SLASH':
                        self.socket.close()
                        return {'FINISHED'}
                    return {'PASS_THROUGH'}


            bpy.utils.register_class(socketModal)

AnimationController.py

            import bpy
            import json

            shapeKeyNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'X']
            dataPathPrefix = "key_blocks[\""
            dataPathSuffix = "\"].value"


            def getShapeKeyAnimationInfo(mouthCue, fps):
                frames = (float(mouthCue['end']) - float(mouthCue['start'])) * fps
                frames = round(frames)
                # print(frames)
                phoneme = mouthCue['value']
                for name in shapeKeyNames:
                    if phoneme in name:
                        return (frames, name)


            def addFaceShapeKeyFrame(shapeKeyName, previousKeyName, startFrame, frameCount):
                faceShapeKeyParent = bpy.data.meshes['face'].shape_keys
                dataPath = dataPathPrefix + shapeKeyName + dataPathSuffix
                faceShapeKeyParent.key_blocks[shapeKeyName].value = 0.0
                faceShapeKeyParent.keyframe_insert(dataPath, frame=startFrame)
                faceShapeKeyParent.key_blocks[shapeKeyName].value = 1.0
                finalFrame = startFrame + frameCount
                if previousKeyName != None and previousKeyName != shapeKeyName:
                    dataPathPrevious = dataPathPrefix + previousKeyName + dataPathSuffix
                    faceShapeKeyParent.key_blocks[previousKeyName].value = 0.0
                    faceShapeKeyParent.keyframe_insert(dataPathPrevious, frame=finalFrame)
                faceShapeKeyParent.keyframe_insert(dataPath, frame=finalFrame)


            def addJawShapeKeyFrame(shapeKeyName, previousKeyName, startFrame, frameCount):
                jawShapeKeyParent = bpy.data.meshes['jaw'].shape_keys
                jawKeyName = getJawKeyName(shapeKeyName)
                dataPath = dataPathPrefix + jawKeyName + dataPathSuffix
                previousJawKeyName = None
                if previousKeyName:
                    previousJawKeyName = getJawKeyName(previousKeyName)
                    dataPathPrev = dataPathPrefix + previousJawKeyName + dataPathSuffix
                finalFrame = startFrame + frameCount
                print (jawKeyName , previousJawKeyName)
                if previousJawKeyName != None and previousJawKeyName != jawKeyName:
                    jawShapeKeyParent.key_blocks[jawKeyName].value = 0.0
                    jawShapeKeyParent.keyframe_insert(dataPath, frame=startFrame)
                    jawShapeKeyParent.key_blocks[previousJawKeyName].value = 0.0
                    jawShapeKeyParent.keyframe_insert(dataPathPrev, frame=finalFrame)
                jawShapeKeyParent.key_blocks[jawKeyName].value = 1.0
                jawShapeKeyParent.keyframe_insert(dataPath, frame=finalFrame)


            def getJawKeyName(shapeKeyName):
                if shapeKeyName in ['D', 'H']:
                    return 'W'
                if shapeKeyName in ['C', 'E', 'F', 'G']:
                    return 'M'
                if shapeKeyName in ['X', 'A', 'B']:
                    return 'C'


            def main(context, data):
                scene = context.scene
                scene.render.fps = 60
                fps = scene.render.fps
                clearAllAnimation()
                phonemes = json.loads(data)
                mouthCues = phonemes['mouthCues']
                previousKey = None
                framecounter = 1
                for x in mouthCues:
                    animationData = getShapeKeyAnimationInfo(x, fps)
                    addFaceShapeKeyFrame(animationData[1], previousKey, framecounter, animationData[0])
                    addJawShapeKeyFrame(animationData[1], previousKey, framecounter, animationData[0])
                    previousKey = animationData[1]
                    framecounter += animationData[0]
                scene.frame_set(1)
                scene.frame_end = framecounter + 1
                bpy.ops.screen.animation_play() # Here is where the animation starts playing 


            def clearAllAnimation():
                faceShapeKeyParent = bpy.data.meshes['face'].shape_keys
                faceShapeKeyParent.animation_data_clear()


            if (__name__ == "__main__"):
                main()

I can calculate the time required for the animation by multiplying the FPS with the total number of frames added but how do i stop the animation after that time without blocking blender UI

$\endgroup$
  • $\begingroup$ You can check context.scene.frame_current. Have you tried bpy.ops.screen.animation_cancel()? $\endgroup$ – sambler May 2 '17 at 9:05
  • $\begingroup$ yea , but how do i call this after the animation has reached a particular frame ?. or after a particular time $\endgroup$ – B0rn2C0de May 2 '17 at 9:18
  • $\begingroup$ You prob. have this working now. I'd suggest not using animation play / cancel at all, rather use scene.frame_set(frame) where you calculate time, and convert to frame in the operator. event_timer_add(1.0,...) makes the timer click, i.e. send a timer event, every 1.0 seconds. $\endgroup$ – batFINGER Aug 21 '17 at 13:36
1
$\begingroup$

Calling some operators from within an operator can have problems, but in your case there is nothing special needed. You can simply call the operator as if it was a normal python function.

def execute(self, context):
    print ("execute")
    self._timer = context.window_manager.event_timer_add(1.0, context.window)
    context.window_manager.modal_handler_add(self)
    bpy.ops.screen.animation_play()
    return {'RUNNING_MODAL'}

def modal(self, context, event):
    print ("modal")
    if event.type == "TIMER":
        print ("no data, just testing")
    if context.scene.frame_current > 100:
        bpy.ops.screen.animation_cancel()
        return {'FINISHED'}
    return {'PASS_THROUGH'}
$\endgroup$
0
$\begingroup$

You can use handlers to respond to events, you don't even need a modal operator for this.

import bpy

def stop_at_last_frame(scene):
    if scene.frame_current == scene.frame_end:
        bpy.ops.screen.animation_cancel()

bpy.app.handlers.frame_change_pre.append(stop_at_last_frame)
print('Registered handler')
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.