Skip to main content
2 of 2
Fixed formatting
quellenform
  • 39.6k
  • 10
  • 56
  • 149
bl_info = {
    "name": "PoseCam",
    "description": "Real-time animation from PoseCam app",
    "author": "Cinesaibai",
    "blender": (3, 3, 1),
    "location": "3D View > Tools",
    "category": "Animation"
}

import time
import json
import socket
import bpy
import mathutils
import select
import threading
import traceback
import re


from bpy.props import (StringProperty,
                        BoolProperty,
                        IntProperty,
                        FloatProperty,
                        FloatVectorProperty,
                        EnumProperty,
                        PointerProperty)
from bpy.types import (Panel,
                        Menu,
                        Operator,
                        PropertyGroup)

running = False
record = False


#dfalseef getBonePropertyName(boneName):
    #return re.sub(r'_([a-z0-9])', lambda x: x.group(1).upper(), boneName).replace("Joint", "")

PORT_NUM = 8080
HOSTNAME = socket.gethostname()
IP = socket.gethostbyname(HOSTNAME)

handshake = json.dumps(
    {
        'HANDSHAKE': {
            'name': 'Blender',  # will be displayed in the app
            'PF': 0,
            'rig': 'UE4',  # Options: 'UE4', 'Mixamo'
            'mode': 'Desktop',  # Options: 'Room', 'Desktop', 'Portrait', 'RoomBodyOnly', 'PortraitBodyOnly'
            'mirror': 'YES',  # Options: 'YES', 'NO'
            'syncFPS': 60, 
            'cameraFPS': 60, # Tries to set camera to this speed. 30 and 60 work on most iphone cameras
        }
    }
).encode()


class ServerThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.running = True

    def resumeServer(self):
        self.running = True
        self.server.running = True
    
    def stopServer(self):
        self.running = False
        self.server.running = False

    def run(self):
        try:
            self.server = Server()
            while self.running:
                self.server.receive()
                
        except:
            pass


class Server:
    def __init__(self):
        self.server_socket = None

        
       
    def receive(self):
      #while self.running:
        connections = {}
       
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.server_socket.bind(('0.0.0.0', PORT_NUM))


        message, address = self.server_socket.recvfrom(65304)
        message = message.decode('utf-8')

        decoded_message = json.loads(message)

        if 'sessionUUID' in decoded_message.keys():
        # is initial connection, send it the handshake
             print(decoded_message, address)
             connections[decoded_message['sessionUUID']] = address
             self.server_socket.sendto(handshake, address)
        
        if 'Body' in decoded_message.keys() and 'Rotations' in decoded_message['Body'].keys():
             
          
             head_rotations = decoded_message['Body']['Rotations']['head']
             quaternion = mathutils.Quaternion(head_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["head"].rotation_quaternion = quaternion
       
             spine_rotations = decoded_message['Body']['Rotations']['spine_01']
             quaternion = mathutils.Quaternion(spine_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["spine"].rotation_quaternion = quaternion

             chest_rotations = decoded_message['Body']['Rotations']['spine_02']
             quaternion = mathutils.Quaternion(chest_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["chest"].rotation_quaternion = quaternion

             neck_rotations = decoded_message['Body']['Rotations']['neck_01']
             quaternion = mathutils.Quaternion(neck_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["neck"].rotation_quaternion = quaternion

             shoulder_l_rotations = decoded_message['Body']['Rotations']['clavicle_l']
             quaternion = mathutils.Quaternion(shoulder_l_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["shoulder.L"].rotation_quaternion = quaternion

             shoulder_r_rotations = decoded_message['Body']['Rotations']['clavicle_r']
             quaternion = mathutils.Quaternion(shoulder_r_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["shoulder.R"].rotation_quaternion = quaternion 

             shoulder_r_rotations = decoded_message['Body']['Rotations']['upperarm_l']
             quaternion = mathutils.Quaternion(shoulder_r_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["upper_arm.L"].rotation_quaternion = quaternion 

             shoulder_r_rotations = decoded_message['Body']['Rotations']['upperarm_r']
             quaternion = mathutils.Quaternion(shoulder_r_rotations)
             quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
             bpy.data.objects["Armature"].pose.bones["upper_arm.R"].rotation_quaternion = quaternion
             
        self.running = True
     
        
 
 
    
    

# This is a global so when we run the script again, we can keep the server alive
# but change how it works

class MyProperties(PropertyGroup):

    running: BoolProperty(
        name = "server_running",
        description = "Is server running?",
        default = False
    )

    skeleton: StringProperty(
        name = "Armature",
        description = "Select an armature",
        default = "",
        maxlen = 1024
    )

    record: BoolProperty(
        name = "record_running",
        description = "Is record running?",
        default = False
    )
   
  

def receivedMotionData(decoded_message):

    if 'Body' in decoded_message.keys() and 'Rotations' in decoded_message['Body'].keys():
        head_rotations = decoded_message['Body']['Rotations']['head']
        quaternion = mathutils.Quaternion(head_rotations)
        quaternion.w, quaternion.x, quaternion.y, quaternion.z = quaternion[3], quaternion[0], quaternion[1], quaternion[2]
        bpy.data.objects["Armature"].pose.bones["head"].rotation_quaternion = quaternion
    
        pass 


def startOrStopServer(running = False):
    if running:
        serverThread.stopServer()
        print("Server is running. Stop it.")
    else:
        print("Start server")
        serverThread.start()

def startServer():
    try:
        if serverThread.running == False:
            serverThread = ServerThread()
            serverThread.start()
            print("Starting server")
        else:
            print("Server already running, using new motion handler.")
    except:
        serverThread = ServerThread()
        serverThread.start()
        print("Starting server")




# = Operators =
class WM_OT_StartServer(Operator):
    bl_label = "Start or Stop Server"
    bl_idname = "mocap.start_server"

    def execute(self, context):
        global serverThread
        global running
        if running:
            running = False
            try:
                serverThread.stopServer()
            except:
                print("Nothing to stop")
        else:
            running = True
            serverThread = ServerThread()
            serverThread.start()
            
        return {'FINISHED'}


class RecordingOperator(bpy.types.Operator):
    bl_idname = "object.recording_operator"
    bl_label = "Record MOCAP or STOP"
    
    
    def insert_all_keyframe(self):
     
   
    
        armature = bpy.data.objects["Armature"]
        head_bone = armature.pose.bones["head"]
        head_bone.keyframe_insert(data_path="rotation_quaternion", frame = bpy.context.scene.frame_current)
       
        armature = bpy.data.objects["Armature"]
        spine_bone = armature.pose.bones["spine"]
        spine_bone.keyframe_insert(data_path="rotation_quaternion", frame= bpy.context.scene.frame_current)

        armature = bpy.data.objects["Armature"]
        neck_bone = armature.pose.bones["neck"]
        neck_bone.keyframe_insert(data_path="rotation_quaternion", frame= bpy.context.scene.frame_current)


        armature = bpy.data.objects["Armature"]
        chest_bone = armature.pose.bones["chest"]
        chest_bone.keyframe_insert(data_path="rotation_quaternion", frame= bpy.context.scene.frame_current)


    def execute(self, context):
       
        
        
        global record
        if record:
            record = False
            bpy.ops.screen.animation_cancel(restore_frame=False)
            
            
        else:
            record = True
            self.insert_all_keyframe()
            bpy.ops.screen.animation_play()
        
        return {'FINISHED'}



   
# = Panel in Object Mode =
class OBJECT_PT_CustomPanel(bpy.types.Panel):
    bl_label = "MoCap-PoseCam Server"
    bl_idname = "OBJECT_PT_custom_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "MoCap"
    #bl_context = "scene"

    @classmethod
    def poll(self, context):
        return context.object is not None
    
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        mytool = scene.my_tool

        global running
        global record
        
        btn_title = "Stop Server" if running else "Start Server"
        btn_icon = "SNAP_FACE" if running else "PLAY"
        btn_title2 = "Stop record" if record else "Start record"
        btn_icon2 = "SNAP_FACE" if record else "PLAY"
        layout.label(text=IP, icon="MOD_WAVE")
        layout.operator("mocap.start_server", text=btn_title, icon=btn_icon)
        layout.prop_search(scene, "skeleton", bpy.data, "armatures", text="Skeleton")
        layout.operator("object.recording_operator", text=btn_title2, icon=btn_icon2)
       
        layout.separator()
        

   
# = Registration =

classes = (
    MyProperties,
    WM_OT_StartServer,
    OBJECT_PT_CustomPanel,
    RecordingOperator
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    bpy.types.Scene.my_tool = PointerProperty(type=MyProperties)
    bpy.types.Scene.skeleton = StringProperty()
    
    
def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
    del bpy.types.Scene.my_tool
    del bpy.types.Scene.skeleton
    

# = Test =
if __name__ == "__main__":
    register()