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