Opening Up Houdini

There are times when you want to get information from other applications, or your own, into Houdini. Although there are many useful operators for loading and exporting information, opening up a port offers more or additional functionality.

A while back I was asked to create a protocol that would enable us to do exactly that in Houdini. This could be from Maya or any other application. In this post I won’t discuss the various serialization methods but will try to open a door on how to start your own (simple) server when launching houdini. One could use this server that runs in the back-ground (in a seperate thread) to directly access information, execute tasks or sample data (in a custom node for example).

A possible use could be to access external data to reconstruct meshes without writing a single file to disk.

To achieve this we need to open a port on startup that can receive data independently from the session a user is working in. Therefore not locking up Houdini.

A couple of files are searched for and run when Houdini is launched. Most noticably 123.cmd, 456.cmd, 123.py and 456.py. 123.cmd and 123.py run when Houdini launches without loading a file. 456.py and 456.cmd are run whenever the session is cleared or a file is loaded. Another option could be to create a file called pythonrc.py. This file is always invoked when Houdini starts up.

For this example I created a file called 123.py and made sure the HOUDINI_SCRIPT_PATH was confifgured correctly to find that file when it launches. If unsure on how to do that, simply create the file in you Houdini script folder.

For example: $HFS/houdini/scripts/456.py > C:/Program Files/Side Effects Software/Houdini 12.0.581/houdini/scripts/456.py. There should already be a file called 123.cmd (the hscript equivalent) in there. Appending your own custom HOUDINI_SCRIPT_PATH location is advisable! When upgrading Houdini, scripts remain accessible.

Once the file has been created we want to start the server when we launch a new Houdini session. I’ve given the server a permanent port to connect to (2000) but it is possible to assign multiple ones for every session running. In this case it means that from wherever you are, you can connect to exactly one Houdini session by sending data over port 2000. Most likely you want to connect to ‘localhost’, which is the computer you are using.

To seperate the object from the initialization steps, a seperate HServer class is created and instantiated in the 123.py file. There can be only one port open and one instance of this class running, making it a singleton. And although Python’s definition of a singleton is weird, it is preferred in this case.

# import our server module
import hdaemon

# start the python server
thread_name = hdaemon.startHPythonServer('python_server')
print "started python sever in thread: %s" % thread_name

These lines of code are executed when houdini launches. When the instance of houdini is exited, the port will close and the thread will stop. But what is actually started, and how? The following piece of code shows what happens when trhe startHPythonServer function is called.

The module ‘hdaemon’ holds the HPythonServer object and 2 methods for starting and stopping it. The start method checks for a thread already running with the name ‘python_server’. If a thread with that name is still active, no new port will be opened and no new process will be returned. If the server is not running, a new HPythonServer is created and started in a seperate thread.

The socket module is used as the low level network interface.

"""
@package hdaemon
This package defines a Python Server that can be used to communicate with Maya
@author Coen Klosters
"""


import threading
import socket
import uuid
import hou

class HPythonServer(threading.Thread):
    """
    Simple Python Server running in a seperate thread within houdini, listerning to commands
    Connect to Houdini using the HPORT specified.
    """

   
    CLIENT_IDENTIFIER = "HPythonServer"
    HOUDINIPORT = 2000
   
    def __init__(self, inClientIdentifier=None ):
        """
        Constructor
        @param self The object pointer
        @param inClientIdentifier A string that allows the overriding of the client identifier.
        """

       
        threading.Thread.__init__(self)     # initialize the thread
       
        if inClientIdentifier is not None:
            self.CLIENT_IDENTIFIER = inClientIdentifier
       
        self.__thread_running = True        # when set to false, cancels the socket to listen
        self.__guid = uuid.uuid4()          # unique identifier name
        self.__associated_socket = None     # Initialize the socket
           
        # Start the thread
        self.setDaemon(True)
        self.start()

           
    def stop(self):
        """
        Close the socket and stop the thread
        @param self The object pointer
        """

        self.__thread_running = False
        self.__associated_socket.close()

   
    def setup(self,inConnection):
        """
        Listens to the connection given to receive external data.
        @param self The object pointer
        @param inConnection The incoming server connection
        """

       
        operation = True
        conn, addr = inConnection.accept()
        print 'Connected to: ', addr
        while self.__thread_running:
            data = conn.recv(4096)          #make sure it's a power of two!
            if data == "print":
                print "switching to print mode"
                operation = True
            if data == "evaluate":
                print "switching to evaluation mode"
                operation = False
                continue
           
            if operation:               # if in print mode, print whatever received
                print data
            else:
                hou.session.dataobject.data = data  # otherwise store the data in the houdini session currently active
               
            if not data: break
           
        print "connection broken with: %s" % str(addr)
       

    def run(self):
        """    
        This is the override of the threading.Thread.run() function which is started on the AssetDaemonPythonClient's thread.
        As long as the __thread_running variable is True we will stay in this function listening for data from the socket.
        @param self The object pointer
        """

       
        HOST = ''                                  # Localhost
        PORT = self.__class__.HOUDINIPORT          # Port to connect to Houdini with
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            s.bind((HOST, PORT))
            s.listen(1)
        except socket.error:
            s.close()
            s = None
            return -1
       
        while self.__thread_running:
            self.setup(s)


def startHPythonServer(inDaemonThreadName):
    """
    Helper function for setting up and starting a HPythonServer
    @param inDaemonThreadName The name to use for the thread containing the client, this name is used to ensure the client works as a singleton
    @return object instance
    """

    mcd_thread = None
    for thread in threading.enumerate():
        if thread.name == inDaemonThreadName:
            mcd_thread = thread
   
    if mcd_thread == None:
        mcd_thread = HPythonServer()
        mcd_thread.name = inDaemonThreadName
       
    return mcd_thread

     
def stopHPythonServer(inDaemonThreadName):
    """
    Helper function for stopping an HPythonServer
    @param inDaemonThreadName The name of the thread that the client should be running in, by using this name the function will find the thread and stop it.
    """

    mcd_thread = None
    for thread in threading.enumerate():
        if thread.name == inDaemonThreadName:
            mcd_thread = thread
           
    if mcd_thread is not None:
        mcd_thread.stop()

When launching Houdini, a seperate thread running alongside Houdini should be waiting for packages to arrive. To connect to Houdini simply specify the server and port you want to connect to. Messages that are send are printed to screen in Houdini.

To connect to Houdini, do the following.

import socket

HOST = 'localhost'                          # The remote host
PORT = 2000                             # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))                 # Connect
s.send("Hellow Houdini, I am your father")      # Send a message
s.close()                       # Close the connection

Off course, sending simple strings doesn’t get you far. In order to actually do something useful, the data needs to be serialized / deserialized. But that’s something I might address some other time. A handy tip is to look in to the hou.session module. This module can be used to store received data and is accessible throughout Houdini! Think of parsing mesh data that can be accessed in a python Sop. Very useful!

One thought on “Opening Up Houdini

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>