UDP stream

Hi all,

i have a udp stream and i try to receive with the timer function the data in blender but it works only if i send data before blender receive it, but it in this cenario i get a time delay.
If i start first blender to recieve data and than send it blender freeze.

import bpy 
import socket
import pickle

import time
HOST = "localhost"
PORT = 5007

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind((HOST,PORT))


def sec():
    data = s.recv(100)
    D = pickle.loads(data)   
         
    return 0.1

bpy.app.timers.register(sec)

its a stream with a dictionary with some points and ids and only 30 fps.
How can i prevent that blender crashes.

You can use a thread to prevent blocking.

import bpy 
import socket
import pickle
import time
import threading

HOST = "localhost"
PORT = 5007

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind((HOST,PORT))

def thread_function(name):
    print("thread start")
    data = s.recv(100)
    D = pickle.loads(data)         
    print("thread return")
    return 0.1

t = threading.Thread(target=thread_function, args=(1,))
t.start()

Thanks again for your help.
I tried this method already


but i am to unexperienced to handle threathing savely in blender especially its permanent open and not closed and i need to interact with the values that i get from the udp stream in the main threat…and i am pretty sure to destroy everything quikly.
Especially this statement from the blender documentation give me this impression.

Use a Timer to react to events in another thread

You should never modify Blender data at arbitrary points in time in separate threads. However you can use a queue to collect all the actions that should be executed when Blender is in the right state again. Pythons queue.Queue can be used here, because it implements the required locking semantics.

Thats the reason i guess the timer function is right joice for a udp sream.

https://docs.blender.org/api/current/bpy.app.timers.html

Yesterday i tried the timer function without threat and queue and blender only freeze as long as the connection is blocked otherwise it works pretty well (except that i have no idea to get variable from the timer function outside of the function).

Anyway thanks for your help…hope i can count on you if i get more questions…till then i have to experiment a lot more to get this working robust.

In simple cases where the scope is small, you can do a sloppy code and abuse threads without any problems.

The problem is when a variable is a dependency in two threads where you will get an exception. As for example I have come across something similar in C#. On the press of a button I run some intense loop that freezes the UI. In order to avoid that UI freeze I place the intense loop inside a thread. However the intense loop will access some variables and objects on the main thread and at some point I get a crash because a variable is accessed at the same time by two thread contexts. So in order to avoid that I just used a thread lock and got the job done without any other special contraption.

The same you can do in Python as well. As for example in the tutorial you can get the idea. However if you have 20 variables you don’t have to lock everything one by one, that would be really troublesome. You can create a context class (pure data class) and lock this instead all at once.

https://www.youtube.com/watch?v=8BMPW49DadA

Perhaps this Queue thing you mentioned might seem a good idea as well. I haven’t look at it yet though.

https://www.youtube.com/watch?v=bnm5_GH04fM

Based on a quick test I made it seems that Blender can handle sloppy threads nicely. This supposed to not happen normally but anyway, since it works it must be good right?

import bpy
import threading
import time
import random
from math import pi, sin

coolVariable = 0
loopStep = 0

def change_cube_position():
    global coolVariable, loopStep
    bpy.context.scene.objects['Cube'].location = (0, 0, sin(coolVariable)*5)
    bpy.context.scene.objects['Cube'].rotation_euler[2] += loopStep
    
def thread_update():
    global coolVariable, loopStep
    loopStep = (2*pi)/100
    for i in range(100):
        coolVariable += loopStep
        print('coolVariable is', coolVariable)
        change_cube_position()
        time.sleep(1/60)

t = threading.Thread(target=thread_update)
t.start()

These tutorial links are very helpful and the Tutor explains it really well.

As for example in the tutorial you can get the idea. However if you have 20 variables you don’t have to lock everything one by one, that would be really troublesome. You can create a context class (pure data class) and lock this instead all at once.

I never used classes and tried to avoid it (its the tomorrow i will learn it mentallity) but i know exactly what you mean by your statement and i have to learn classes.

At the moment looks my code like this.

import bpy 
import socket
import pickle

import time
HOST = "localhost"
PORT = 5057


s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind((HOST,PORT))
Col = bpy.data.collections['Collection 1']

def sec():
    data = s.recv(100)
    D = pickle.loads(data)
    
    for name,loc in D.items():
        # change object location    
        if str(name) in Col.objects.keys():
            loc = loc+(0,)
            bpy.data.objects[str(name)].location = loc
            
        # add object    
        else:
            empty = bpy.data.objects.new(str(name),None)
            loc = loc+(0,)
            empty.location = loc
            Col.objects.link(empty)


        
    # remove objects
    obj_to_remove = set(Col.objects.keys()).difference(str(D.keys()))

    for name in obj_to_remove:
        ob = bpy.data.objects.get(str(name))
        if ob is not None:
            bpy.data.objects.remove(ob, do_unlink=True)
        
    return 0.05

bpy.app.timers.register(sec)

The return value is 0.05 because i have only 15 fps which seams enought.(1/15).
The data is a OrderedDict udp stream and look like this

OrderedDict([(0, (598, 29))])

The dictionary increace and decrease depending how many objects in the view of the realsense depth camera are.
The blender script adds, removes or update position depending on the stream.
I use this collection info in animation node for further calculation.

I am really curious how this will look like on the led wall when everything fits together and the light react to my movements.

My first threats experiments showed me that i can run a threat multiple time if i dont close it.
Changed a cube location with thread in a while looop… ah cool it works…added some more code and hit ctrl + p again and one more time and saw funky cube movments because they all run in the backround together…boom blender crashes… You know i am a noob and noobs do what no professional expect :slightly_smiling_face:.

Based on some tests I did I noticed that using a timer can work fine only if the client (another application) keeps feeding data to the server (Blender). However this means that at any given point when the client disconnects blender will hang.

Another test I did using timers with thread and queues. I noticed severe impact in speed and after stopping the server thread a crash. Not looked at it any more.

The only solution I am satisfied with is only with straight threading. I could even get pure 60fps that looked really good for real time speed. Though I did not place lots of objects into the mix to see any conflicts (though I might add incoming messages onto a stack so they are processed on their own). Also I got no crashes at this point, I might consider that adding thread lock might be a good idea.

Server

import bpy 
import socket
import time
import threading

HOST = "localhost"
PORT = 5007

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))

def process_command(cmd):
    cmdpart = cmd.split("\\")
    if cmdpart[0] == "ChangePosition":
        loc = cmdpart[2].split(",")
        bpy.context.scene.objects[cmdpart[1]].location = (
            float(loc[0]), float(loc[1]), float(loc[2])
        )

def server_function(x):
    print("starting server")
    
    isRunning = True
    
    while isRunning:
        data = s.recv(100)
        text = data.decode("utf-8")
        time.sleep(1/30)        
        if len(text) == 0:
            continue
        
        print(text)
        
        if text == "Quit":
            isRunning = False
        else:
            process_command(text)

    
    print("server stopped")
    return 0.1

t = threading.Thread(target=server_function, args=(1,))
t.daemon = True
t.start()

C# Client

using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace csharpudpclient {
	class Program {

		static void SendMessage(UdpClient client, string message) {
			var messageBytes = Encoding.ASCII.GetBytes(message);
			client.Send(messageBytes, messageBytes.Length);
			Debug.WriteLine("Sent Message => " + message);

		}

		static void Main() {
			var client = new UdpClient(5007);

			try {
				client.Connect("localhost", 5007);
				var rand = new Random();

				for (int i = 0; i < 100; i++) {
					SendMessage(client, $"ChangePosition\\Cube\\0, 0, {MathF.Sin(i*0.5f)*2}");
					System.Threading.Thread.Sleep(16);
				}

				SendMessage(client, "Quit");

				client.Close();
			}
			catch (Exception e) {
				Console.WriteLine(e.ToString());
			}
		}
	}
}

P.S. I am interested as well to get network communication. That way I will be able to stream my models directly into the game at runtime. No one has attempted this ever so I am into deep waters here trying to figure out everything on my own. :neutral_face:

Based on some tests I did I noticed that using a timer can work fine only if the client (another application) keeps feeding data to the server (Blender). However this means that at any given point when the client disconnects blender will hang.

Yep, so my workflow is starting blender (starts hanging) and after that start sender and blender is available again.
But as you said this works only as long as blender recieve data.
So your solution with threating seams really promissing.
Thanks again for all your test an sharing it this is extrem helpful for me and hopefully for everyone else who sit in the same boat of usp streams.

No one has attempted this ever so I am into deep waters here trying to figure out everything on my own.

I feel with you

I will keep an eye on the post as well in case some one else jumps in and offers some info. :slight_smile:

After some time i tried your option with threading and no matter i tried …blender crashes after some interaction with the scene.
So i went back to the timer function option and as you said.

Based on some tests I did I noticed that using a timer can work fine only if the client (another application) keeps feeding data to the server (Blender). However this means that at any given point when the client disconnects blender will hang.

So i tried to check inside the timer function if the data are send or not with the select module.

import bpy
import select
import socket

HOST = "localhost"
PORT = 5037


s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind((HOST,PORT))
s.setblocking(0)

def loop():    
    sel = select.select([s],[],[],0.01)
    if sel[0]:
        data = s.recv(100)
        pkl = pickle.loads(data)
        empty (pkl)
    else:
        pass   
    
                     
    return 0.01

bpy.app.timers.register(loop)

The select module have a timeout which i set to 0.01.
It can be done also with a socket.settimeout and a try except…works also.
In addition i added a button to unregister the timer so the complete code look like this.

import bpy 
import socket
import pickle
import select
import keyboard

import time
HOST = "localhost"
PORT = 5037


s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind((HOST,PORT))
s.setblocking(0)
Col = bpy.data.collections['Collection 1']



def empty(D):        
    for name,loc in D.items():
        # change object location    
        if str(name) in Col.objects.keys():
            loc = loc+(0,)
            bpy.data.objects[str(name)].location = loc
            
        # add object    
        else:
            empty = bpy.data.objects.new(str(name),None)
            loc = loc+(0,)
            empty.location = loc
            Col.objects.link(empty)


        
    # remove objects
    obj_to_remove = set(Col.objects.keys()).difference(str(D.keys()))

    for name in obj_to_remove:
        ob = bpy.data.objects.get(str(name))
        if ob is not None:
            bpy.data.objects.remove(ob, do_unlink=True)
            
            
def loop():
    if not keyboard.is_pressed('q'):
        sel = select.select([s],[],[],0.01)
        if sel[0]:
            data = s.recv(100)
            pkl = pickle.loads(data)
            empty (pkl)
        else:
            pass   
    else:
        print ('quit')
        bpy.app.timers.unregister(loop)
                     
    return 0.01

bpy.app.timers.register(loop)

Very good. I might try this select module as well to see how it works.

By the way I might try to adapt the multi-threading example to Queues just for the sake of getting it right. https://docs.blender.org/api/current/bpy.app.timers.html