Controlling XSI remotely using sockets

December 31st, 2006 by Kim Aldis - Viewed 15216 times -




XSI 6.0 comes with a nice little example of how to connect with XSI remotely. It uses C# and being a little bit dumb and a lottle bit the worse for Christmas wear, I asked on the mailing list if it wouldn’t have been easier to do using Python, it not having dawned on me that it was, well, a C# example. Luc-Eric immediately chucked the gauntlet back again; “show us”, he said. So I will.

It is, of course, more useful than just an example as anyone who’s used Maya’s CommandPort can tell you. The C# example is cleverly wrapped in a timer event, allowing it to run in the background and quietly check for a connection every few seconds. More for reasons of clarity than anything else I’m just going to wrap this up in a function and let someone else decide how best to use it but I will get a little bit into timeouts and how to handle the hang you can get if there’s nothing at the other end.

Python is a really good example of just how sensible it was to use good existing scripting languages rather than writing bad new ones. You not only get the language for free, you also get most of the Open Source community too and with Python that means quite a lot of modules, allowing you to do more stuff than you could shake a stick at in a lifetime of stick shaking.

I’d done stuff like this before in Perl and C but not Python and I was interested in how long it was going to take to do this. As it turned out, around 20 minutes, which is pretty good but more testament to the other thing you get for free with Python – shedloads of free examples – than any skills I may have.

Python is pretty good at wrapping complicated stuff up and making it easy to use and this is no exception. To read something from a port the sequence of events is:

  • Create a socket
  • Bind to the socket
  • Listen on the socket
  • Accept the connection
  • Read whatever’s being sent from the other end
  • Optionally send something back
  • Close the connection

This is all fine and dandy, except that if there’s nothing at the other end then the Accept hangs until there is. If nothing ever tries to connect we get a nasty hang that’s not easy to get out of. So we could do with something that can peek at the socket and see if there’s something there. Such a thing is a thing called “select”. In generic terms Select() is used to check any kind of file that’s open for reading to see if there’s something to read and, at least of grownup operating systems, a socket is treated in the same way as a file. We call select() on the socket just after we start listening. If select returns a list that isn’t empty then someone’s knocking at the other end and it’s safe to go ahead. If it’s not we ignore the accept call and just return nothing and bingo! no hang.

And finally, I’ve taken a slightly different slant in executing code to the C# example. It accepts commands to either log a string or run a script file. It seemed more natural to me to run a string than a file, partly because it seems such a pain creating script files all over the place but mostly because I figured if I could run a string then I could do both the other two things with the code in that string.

Here’s the code. You can test it by running the script in XSI and using telnet to connect. Either “telnet localhost 50007″ from the local machine or “telnet machine 50007″ from some other machine. At the telnet prompt type something like jscript|logmessage(“Testing”); will run logmessage(“Testing”) at the XSI end. Enjoy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import socket
import select
import re
 
def ReadSocket(PORT, TimeOut=1):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', PORT))
    s.listen(1)
 
    # is someone knocking at the other end?
    #
    a = select.select([s], [], [], TimeOut)[0]
 
    # someone's there
    #
    if (a != []):
        # accept the connection
        #
        conn, addr = s.accept()
 
        # get data from the socket
        #
        data = conn.recv(1024)
 
        # parse the result
        #
        tokens = re.split('\|', data)
        if (len(tokens) != 2):
            Application.LogMessage("Useage: Language | script code")
            sRet = "Error"
        else:
            sRet = str(Application.ExecuteScriptCode(tokens[1], tokens[0]))
 
        # be polite and send something back
        #
        conn.send(sRet)
 
        # close the connection and return the result
        # of the last command
        #
        conn.close()
        return sRet
 
    # there was nobody there so return false
    #
    return False
 
###############################################################
#
# Testing on port 50007
# with a 10 second timeout.
# You can test this using "telnet hostname 50007" from any other machine
# or "telnet localhost 50007" from this machine
# typing something like 'jscript | logmessage("testing")' will run 'logmessage("testing")' in the remote XSI.
# 10 seconds should be enough time for you to run across the room and kick off a telnet. If you don't
# make it then it'll just time out.
# The connection is closed once the first string has been parsed but you can, if you wish, concatenate
#  commands, as in 'jscript|logmessage("testing")|python Application.LogMessage("Python Testing")'
#
s = ReadSocket(50007, 10)
if (not s):
    Application.LogMessage("Timeout")
else:
    Application.LogMessage("Connected: %s" % s)

9 Responses to “Controlling XSI remotely using sockets”

  1. Steven Caron says:

    http://pexpect.sourceforge.net/

    here is a module that maybe able to handle many of these types of tasks…

    steven

  2. Kim Aldis says:

    not sure I follow you on this one Steven. How would you use this?

  3. Steven Caron says:

    i may have been premature in my posting. looking now my response might not replace what you have outlined here. so these may work together.

    “…How would you use this?”

    the only use i see for controlling XSI like this would be to extend batch scripting to automate interaction with multiple programs…

    example…

    -start XSI
    -run code to process and export something like a .objs and some cache files to a database
    -start other program (houdini, maya)
    -run code to query database, import the output from XSI, run some procedural tasks, and export some data
    -start XSI again
    -run some code to query database, import the output from ”other program”, and handle the data (final rendering)
    -start composite program or use FXtree in previous session
    -run code to query database to build slap comps from rendered output

    another idea that would only work if the pexpect module can work with multiple apps at once…

    -start 2 instances of XSI
    -export a list of .emdls, .objs, .xsi, etc.
    -as they finish load them in the other instance of XSI
    -run code to preform sanity checks, check for corruption and other failures, render thumbnails for use in a database or a catalouge of your assets

  4. Kim Aldis says:

    I originally came up with the idea when I was thinking of ways to distribute frames around a farm with xsi. The idea was to just kick off XSIbatch on all the machines then send them render commands for individual frames. I scrapped it in the end when I found a better way – the idea of leaving xsi running for thousands of frames didn’t seem appealing – but for anything that requires sporadic processing in XSI when you don’t want the overhead of continually reloading scenes, this would be the way to go.

    You could easily use (p)Expect to drive XSI with little modification of the given code, in fact it’s probably the best way to go because Expect can wait on completion at the other end.

    I also liked the way the SDK example used the timer event to look for inputs on the port in the background. That’s neat and potentially quite useful if you can find a use for it. I think it’s a good idea waiting for a problem, to some degree.

  5. Another way of doing the same thing but easier: use the XMLRPC (XML Remote Procedure Call) libraries and server provided by Python.

    Ex, run this python from inside XSI:

    class MyFuncs:
    def log(self, s):
    Application.LogMessage(s)
    return ‘Done!’

    from SimpleXMLRPCServer import SimpleXMLRPCServer
    server = SimpleXMLRPCServer((“localhost”, 8000))
    server.register_introspection_functions()
    server.register_instance(MyFuncs())
    server.serve_forever()

    # And run this from a console, replace localhost by the name of the computer XSI is running on:
    import xmlrpclib
    server = xmlrpclib.ServerProxy(“http://localhost:8000″)
    server.log (“This is a test!”)

    Cheers,

    Aloys

  6. Nano says:

    I am picking up python scripting, our script helps me a lot to understand some tricks – thanks

  7. Mr_Nitro says:

    hi,
    I’m trying to mess with xsi ‘intelligent’ object control… trying to build up a meta-language for querying or performing task remotely on a scene, but I’d like to still have manual control on the scene itself while I telnet to the inside-xsi python server, what happens is that when I enter the server main loop xsi gets stuck with it until I quit the connection, I wonder if there’s some way to have it go ‘remote’ only when a socket event happens… maybe that Pexpect can do that? or it takes full control of the spawned apps?

    cheers

  8. Kel Solaar says:

    Hello o/

    I tried the remote socket connection, and Alloys Baillet technique, in both of the case XSI is stuck/frozen and only reacting to remote commands, is there a way to make it like maya with it’s commandPort command?

  9. Kel Solaar says:

    Hi,

    A few years after, and bored by recompiling the SDK example, I decided to crack the nut: https://github.com/KelSolaar/TCPServer_For_Softimage

    And the announcement thread on XSI Base: http://www.xsibase.com/forum/index.php?board=29;action=display;threadid=46343

    Enjoy!