Mainly, the absence of a Tree widget, the clunkiness of the Grid widget, the need for finer control on callbacks for complex UIs, the difficulty of having session-persistent UI (ones that don’t get trashed on a New Scene).

The main goal of being able to write wxPython UIs inside XSI is for pipeline tools. I would not try to do better than Softimage for animation controls for example. But when it comes to streamline your pipeline, having consistent UI across your applications will save a lot of development time and user frustration.

In the Python XSI plugin called wxPythonXSIExplorer.py I implemented a really crude XSI Selection Explorer. This is an highly useless tool, but it shows how this is possible to nicely interact with XSI using a set of basic wxPython widgets. Please consider the explorer itself as a proof-of-concept tool!

The interesting part of this plugin is the class called XSISubFrame. This is the class that implements all the gritty details described below, and I highly recommend you start your new wxPython XSI frame by looking at how I inherited from this class to create the XSIExplorerFrame. And how I use the XSISubFrame.create static factory method to create a new instance of the explorer.

I also implemented a SelectionChange event to detect the selection change in all the running instances of the wxPython XSI Explorer. Unfortunately there is no general hook for data change in XSI so if you move an object, the parameter value won’t update in our explorer.

And now for the gritty details…

The base trick to get a wxPython frame nicely displayed inside XSI, meaning on top of the XSI main window, but behind other XSI views when they have the focus, is to create the main frame as a child of the XSI top-level window. And for that we need to get its MS Window Handle.

The pywin32 package is here to help us: we can use it to list all the running windows and filter them by process ID, then pick the one which names begin with SOFTIMAGE. It sounds like a hack, but it’s just a small hack, believe me, MS Windows programming can get far more hacky than that!

The real magic happens when you use the AssociateHandle method on a dummy wxFrame so that wx believes that the XSI top level window is an actual wx window…

Then we just create our own sub frame using the wx.FRAME_FLOAT_ON_PARENT flag, which does what we want.

Then don’t forget to call DissociateHandle on your dummy frame, if not, XSI will not get some critical events anymore, like EVT_CLOSE, meaning you can’t close XSI anymore…

You will also need to do some cleanup when closing the wx window. The main problem is that wxWindow::Destroy does not destroy the top level window, it just waits for the
wxApp to destroy it on it’s next event loop. But as we don’t have a wx loop running, we need to destroy the top window by hand using the win32gui module again.

Some warnings!

wxPython is great, but it can crash easily if you do something wrong. In XSI 6 where there is no standard output from Python displayed in the script logging window (as there is in 5), it’s better to handle all exceptions, and display them yourself, or you might miss some important message from wxPython that causes a crash in the app (see line 370 of the plugin).

This is Windows-Only stuff for now!

Fortunately for Windows users, the wxWindows framework uses standard MS Windows event loop, and wxPython integrates amazingly well inside XSI. I haven’t tried under Linux, but the big problem over there is that wxWindows is using GTK as its UI framework, and I don’t think MainWin and GTK will go very well together unless Softimage builds a version of wxMSW using their version of Mainwin and give it to us…

BTW, wxPython works well in Maya as well, using the still experimental python for Maya plugin from cgkit, meaning that mixed pipeline could use the exact same UI in XSI and Maya!

Some native widgets have been “hacked” by Luc-Eric and his team to look better, the base TextCtrl is one of them. If you use it, its background will never refresh at all. I didn’t find an easy way of making it refresh. But you can get a nice text control by using the TE_RICH2 style, which has not been hacked. I provide a class called XSITextCtrl in my plugin that sets the style automatically as well as the background color.

Modality is a problem… The problem is that wx and XSI do not know well each other. If a wx dialog is shown modal, you can still work in XSI, and if a XSI dialog is shown modal (lik a new scene prompt), you can still do stuff in wx. And that could be really dangerous!

I worked around it by providing a custom showModalDialog method that disactivate the XSI top level window while the wx modal dialog is shown. And I implemented a EVT_ACTIVATE event to detect if there is a modal dialog running in XSI and I disable the content of the wx activated window until the top level window is activated.

This is not perfect, but it gives the right functionality!

Conclusion

This is surprisingly possible to seamlessly run wxPython UIs inside XSI!

I hope you will find some use to this and push Softimage to support this unknown feature, especially under Linux.

Have fun!

Aloys Baillet
R&D, Animal Logic

This entry was posted
on Wednesday, January 10th, 2007 at 9:47 pm and is filed under Python, User Interface.
You can follow any responses to this entry through the RSS 2.0 feed.
Both comments and pings are currently closed.

This is real useful stuff Aloys. I tried using PerlTK a long ways back but it just hung up.

A question for you, if you’ve dug around in wxPython at all. I’ve for ages been looking for something I can use to represent a hierarchy or graph. Not really an explorer type thing but something rather more like,say, the schematic. I want to be able to view relationships. Anything like that you’ve seen?

Hey, that’s cool, I’ve done some similar experimentations with wxWidgets in C++, but the problem was that I couldn’t figure out how to remove the XSI interface look because it was buggy on some widgets.
Don’t you know how to achieve that ?

So I’ve tried with Qt, and it worked fine except I had to launch the app in a new thread because of the Qt main loop event that blocks the use of XSI.
I’ve read in an interview on XSIBase that blur are taking a look at Qt option.

Kim: unfortunately those complex widget are not easy to find… with some work you could use the OGL module of wxPython, it does not look really good, but it’s an editable graph.
For the display only of graphs, you could use the graphviz python library called pydot: http://dkbza.org/pydot.html

Julien: yes wxWindows in C++ should be straightforward to use inside XSI. I wanted a easy to use solution with wxPython only, that’s why I didn’t even try wxWindows.
What should be possible as well is to use a combination of wxWindows and embedded wxPython.

Christopher: I like the fact that the widget are “XSI-like”, I think it would be less disruptive for the user. I just tried the SuspendWin32ControlsHook trick, and it works!
The problem if you use a different thread to run your GUI is that you will sure get into a dead lock at some point and get a crash, as the XSI SDK is not thread-safe.
The way I start the wxFrame makes it completely integrated with the XSI event loop and unless you don’t play too much with wxThread, wxPython in XSI should be crash-free.

Patrick: Thanks for the plugin link!
I was using the exact same technique to redirect stdout and stderr in XSI 4.2, but I ended up removing that after some weird failure of automated XSI jobs on the farm… The fact that XSI 5.11 was giving us that for free was a good thing that we lost in XSI 6, Luc-Eric tried to bring it back for us, but it was not possible.

Luc-Eric: Thanks for the trick, it seems to work perfectly!
I created a C++ command to enable disable the Win32ControlsHook that I call before and after creating wxPython widgets.
But I must confess I prefer the rounded buttons… Do you know if it’s possible to use the rounded text controls from wx?

The problem I had with the XSI look were some interactions issues with widgets like the grid. I don’t remember what exactly, but it was some annoying problems like as clicking two times in a cell to edit it’s value, or when editing the value the text was white with a white background, …
I didn’t used any thread with wx, because the main loop event was correctly handled by XSI. I just had to create a thread for Qt.

Hi! I’m trying to make a window with pygtk on linux. The problem is that the main loop event blocks XSI so i’ve created a thread with the event loop. But the thread doesn’t see the XSI sdk (it can’t see Application and so on). What do i have to do to create the thread?

Ana:
I’m not familiar with XSI on Linux nor pyGTK but you could try passing the result of the globals() function to your new thread’s constructor. The Application object and it’s siblings are defined in the script editor’s global scope so, especially if your thread is defined in an external module that you import, your thread might not have access to the appropriate scope.

It haven’t worked. Anyway, thanks.
Application is visible in the constructor but not in the run function. And if i pass globals to the constructor and i save it as an attribute of the class, when i try to get the element Application from this attribute in the run function it returns None.
I’ll keep trying and i want to try another solution without threads.