As part of the project I’m working on to deskew perspective images and insert them as RasterImage entities inside AutoCAD, I spent quite some time migrating pure Python code – a good deal of which I had to create for various assignments as part of the linear algebra class I’ve now finished – for it to work inside IronPython. For those of you who aren’t familiar with it, IronPython is a variant of Python that works with .NET via the Dynamic Language Runtime. Making it really easy to integrate with AutoCAD.
The code as it stood previously was working inside CPython, which I used on my Mac to complete the assignments for this course:
And it worked pretty well: I’d previously spent quite a bit of time getting the code in a reasonable shape for porting to other environments (placing loose code in functions, etc).
As mentioned previously, one of the big issues to resolve was to make sure my code wasn’t dependent on compiled Python (.pyc) files, as these currently don’t work inside IronPython. With that particular issue resolved – we had to implement our own “solver” module in one of the later homework assignments, effectively replacing the compiled one we’d been given previously – it remained to get cracking on getting the .py modules themselves to work inside IronPython.
The most straightforward way to get moving on this was to use the Python REPL – provided as IPY.EXE, a command-line application – to try out the code and look at the results (and the errors… the errors… :-). My aim was to build a single codebase that would work via the command-line in CPython, with standalone IronPython (i.e. IPY.EXE) and then inside IronPython hosted by an AutoCAD app. And the Python code inside AutoCAD should integrate with its the UI, sending messages to the user via the command-line and displaying the progress of long-running operations via the progress meter in the status bar.
The first part of this was to get the .py files available to the IPY executable. Now there may well be a more elegant way to deal with this – in fact, there must be – but I just went ahead and copied them into the “C:\Program Files (x86)\IronPython 2.7” folder. When I then launched IPY I could load and execute them:
Much of the code worked well enough, but there were some really subtle issues that took me time to identify and fix. One that took me ages to find was due to an operator inside the matrix class: the operator checks the type of the arguments in order to determine whether to use matrix-vector multiplication, matrix-matrix multiplication or simple scalar multiplication. Because the type test it was performing – e.g. “Mat == type(other)” – was always failing, the code always fell through to scalar-matrix multiplication. I eventually fixed it by using “isinstance(other, Mat)”, which worked in the various flavours of Python in which I tested it.
Being in a dynamic environment certainly has its challenges: as the type you’re expecting from a certain operation – whether it’s a matrix-vector multiplication that should return another vector, a scalar multiplication on a matrix that should return another matrix, etc. – doesn’t always get returned and doesn’t necessarily cause a problem there and then. It’s only as the data flows onwards that the problem surfaces in what often turns out to be a really obscure way.
And the problematic results are often really hard to troubleshoot: there wasn’t a straightforward way for me to debug into this code inside either IPY or AutoCAD, at least not that I could find.
Another “fun” one was due to the accumulator for a vector-vector dot product operation being initialised to an integer (0) rather than a decimal (0.0). In IronPython this meant the results would always be returned as an integer, while in CPython the data-type morphed to a decimal as it needed to. Finding that one was also really tricky, although it did create some pretty cool images in the meantime (creating anything at all was exciting, by this point, and besides that I liked the effects ;-).
Eventually it all worked well, which meant I could also successfully build the code into an app hosted inside AutoCAD. We’ll take a look at that in the next post in this series. In the meantime, here’s a ZIP containing the Python code that you can run for yourself in IronPython or CPython before seeing the final version working inside AutoCAD. Check deskew.py for the various tests that are included (deskew2()… deskew4(), in particular).
Before I wrap this post up, I’ll give a quick update on one of the later tasks, specifically the one to create a UI allowing selection of the four corner points to feed the de-skewing code. I’d previously thought to create a WPF app for this but I’ve now changed my mind: in order to create something that’s ultimately more flexible – an asset that could be repurposed much more broadly – I’ve decided to create something inside HTML5 and JavaScript.
Which means that by the time we’re done, we’ll have an app that’s running C#, Python and JavaScript code (and dealing with the various interop issues this combination will raise). Fun, fun, fun! :-)