Hi, and Happy New Year!
It’s my first post in here, so I take the opportunity to thank Patrick for this great XSI site!
You’ll find here some nice discoveries I made trying to use wxPython inside XSI. For that I will assume that you, dear Reader, have some knowledge of Python and UIs…
Animal Logic is also kindly giving you an example XSI Plugin that should be a good starting point if you want to implement a wxPython interface in XSI.
I apologize in advance for the technical and boring stuff ahead… it’s the unfortunate price to pay to get nice and shiny UIs!
You can download the plugin here.
But why would one do that, Custom Properties should be enough?
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.
- XSI version 5.11 or 6
- WinXP 32 (win2k supported)
- Python (2.5 recommended, 2.4)
- pywin32 210
- wxPython (126.96.36.199 recommended, older supported)
A look at the plugin!
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.
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…
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!
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.
R&D, Animal Logic