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()