Communication with Blender via sockets
I’m always curious about expanding functionality and improving workflow by connecting devices to software that I already enjoy using. I love using Blender and making custom controllers for creating computer graphics is something very interesting to me.
As long as a program has a scripting environment or a plugin support, you can usually connect anything you like to it. In one of my previous experiments I’ve added a serial communication between Blender and a custom device. It was a bit clunky, obviously. Serial communication isn’t something you want to be run as a Blender’s subprocess.
A slightly more civilized solution would be to spawn a server instance on Blender’s side and poll for incoming messages. I’ve tried to implement something like that with sockets module. The idea was to see if a solution like that would mess with the primary process and how well does it handle communication with an outside world.
You’ll find the code below:
import socket
import sys
import bpy
HOST = '127.0.0.1'
PORT = 65432
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
# listen() marks the socket referred to by sockfd as a passive socket (awaits for an incoming connection,
# which will spawn a new active socket once a connection is established), that is, as a socket that
# will be used to accept incoming connection requests using accept(2).
s.listen()
# Extracts the first connection request on the queue of pending connections for the listening socket,
# sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket.
# The newly created socket is not in the listening state. The original socket sockfd is unaffected by
# this call.
conn, addr = s.accept()
conn.settimeout(0.0)
def handle_data():
    interval = 0.1
    #print('Connected by: ', addr)
    data = None
    # In non-blocking mode blocking operations error out with OS specific errors.
    # https://docs.python.org/3/library/socket.html#notes-on-socket-timeouts
    try:
        data = conn.recv(1024)
    except:
        pass
    if not data:
        pass
    else:
        conn.sendall(data)
       
        # Fetch the 'Sockets' collection or create one. Anything created via sockets will be linked
        # to that collection.
        collection = None
        try:
            collection = bpy.data.collections["Sockets"]
        except:
            collection = bpy.data.collections.new("Sockets")
            bpy.context.scene.collection.children.link(collection)
        if "cube" in data.decode("utf-8"):
            mesh_data = bpy.data.meshes.new(name='m_cube')
            obj = bpy.data.objects.new('cube', mesh_data)
            collection.objects.link(obj)
        if "empty" in data.decode("utf-8"):
            empty = bpy.data.objects.new("empty", None)
            empty.empty_display_size = 2
            empty.empty_display_type = 'PLAIN_AXES'
            collection.objects.link(empty)
        if "quit" in data.decode("utf-8"):
            conn.close()
            s.close()
    return interval
bpy.app.timers.register(handle_data)The script starts by importing necessary modules and creating a TCP socket, binded to the
              localhost
              on port
              65432.
Then a bit of sockets magic happens (it’s not that magical or complex…). A socket is instantiated.
That socket waits for a connection, and as soon as a client connects it creates another socket,
called
              conn
              (for
              connection).
The next interesting part (skip the
              handle_data
              definition for a second) is a call to
              Blender’s
module
              bpy.app.timers.register(handle_data). This function registers a timer which will fire the
              handle_data
              function. That function needs to return time, in seconds, after which the timer should
execute it again.
The
              handle_data
              checks if there is any data to be processed. If it finds keywords:
              empty
              or
              cube
              in the message, it creates a new
              Empty
              object or a
              Cube
              mesh. Those will be put under
              Sockets
              collection.
It also recognizes a
              quit
              message. I’ve implemented that because after you run the script in
              Blender’s environment you loose control. I’ve been iterating on the script and not closing the
socket correctly meant that it was still there, after the script exited, binded to the port.
That meant that I had to restart
              Blender.
This script is very rough. You have to start it manually every time you want to connect again. The
              s.accept()
              is a blocking operation which means
              Blender
              hangs, until a client connects. As I’ve
mentioned it was a test, an experiment. It does work and doesn’t impact the
              Blender’s main loop.
You can test that script by running it from the
              Blender’s scripting tab, starting
              netcat
              in a
terminal like so:
nc localhost 65432That opens a prompt in which you can type the commands. You type
              cube, smash that
              Enter
              key and
              Blender
              spawns a cube.
I wish the idea of interprocess communication was a part of the core design of applications like Blender. It’s very common to have a scripting environment in apps like that. They always feel a bit hacky and tacked on.