1
$\begingroup$

Can someone help me understand why this code:

import socket
import bpy
import mathutils

PORT_NUM = 8207


''' Prints your local IP address.  Configure this in the App.
  Make sure your router and firewall do not block the port '''
def show_my_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('10.255.255.255', 1))
        ip = s.getsockname()[0]
    except Exception:
        ip = '127.0.0.1'
    finally:
        s.close()
    print("Connect the app to IP:", ip, "Port:", PORT_NUM)
    
  


# The handshake configures the app
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, # App smooths processed frames to constant FPS.  0 for async mode (will still smooth joints between processed frames)
            'cameraFPS': 60, # Tries to set camera to this speed. 30 and 60 work on most iphone cameras
        }
    }
).encode()

show_my_ip()

connections = {}
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(('0.0.0.0', PORT_NUM))

while True:
        message, address = 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
        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']
    
        # Use head_rotation to set rotation of head bone in Blender
        bpy.data.objects["Armature"].pose.bones["head"].rotation_quaternion = mathutils.Quaternion(head_rotations) 

it gives me no errors, but Blender crashes and I don't receive any data.

It receives data, if I eliminate the while True loop, but exits the loop immediately.

$\endgroup$
1
  • $\begingroup$ Hello and welcome. Due to the while loop running infinitely, the script is blocking thus you cannot interact with the Blender interface. If u try to do something else like clicking on something in the interface while the script is running, it can cause Blender to crash. $\endgroup$
    – Harry McKenzie
    Commented Jan 18, 2023 at 22:50

3 Answers 3

1
$\begingroup$

Your script allows Blender to receive motion capture data from a mobile device over a network connection. It uses the socket library to create a server that listens on a specific port (8207) for incoming data. The function show_my_ip() is used to print out the local IP address of the computer running the script, so that the user can connect to it from their mobile device.

When the script receives data, it checks if it is an initial connection by looking for the sessionUUID field in the received data. If it is an initial connection, it sends a handshake message containing configuration information to the mobile device.

The script then checks if the received data contains body rotation information, specifically in the Rotations field of the Body field. If it does, it uses the mathutils and bpy library to set the rotation of the head bone in Blender based on the rotation data received.

This allows the user to capture the movement of a person with a mobile device and apply that movement to a character model in real-time.

It looks like the while loop is causing the crash. Try to print out some text into the console using print() to debug and find out where the script stops. Make sure you have the System Console open with Window > Toggle System Console.

Also take note that due to the while True loop, your script is running in an infinite loop checking and processing data which means it is blocking, thus you won't be able to interact with the Blender interface. If you are looking for a non-blocking solution try to use a timer or the Asyncio module. See this thread for more server/client script ideas.

$\endgroup$
2
  • $\begingroup$ "Meanwhile, thank you, I've evaluated another approach, I've created an add-on and now it works well, but I have a question about it, I've created a button to record the quaternion values on input, but it only records the values in the timeline when I press the button and not while it's moving, do you know where I'm going wrong based on this code? Thank you." $\endgroup$ Commented Jan 21, 2023 at 16:11
  • $\begingroup$ Sorry, but I'm unable to correctly insert all the code. $\endgroup$ Commented Jan 21, 2023 at 16:24
0
$\begingroup$
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'}
$\endgroup$
0
$\begingroup$
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()
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .