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 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.
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!
thank you for the great example