I’ve been dying for some time now to write a post explaining some of the changes I’ve been making to Blender’s DirectX exporter. In some senses it’s nothing particularly exciting, but it’s been a great learning experience for me, and it’s unearthed some little nuggets of goodness that I just can’t help but share.
The Importance of Feedback
I’ve been developing an engine to make 3D Point & Click adventure games for a few years now, and I have to admit that for much of that time I’ve been in a state of denial about how hard it would be to get content out of my 3D modelling package, Blender, and into my game. I’d only ever used pre-existing .X files for all my testing (good old Tiny.x!) and since Blender ships with a Python script for exporting DirectX files I wrongly assumed it be trivial to start creating my own when the need arose.
However, when I eventually tried it, it seemed to fail every time. The author was great at replying to my emails, and he was always able to diagnose some obvious flaw in the way my mesh was configured that was causing the exporter to stumble – “you’ve got a negative scaling factor”, “your armature isn’t parented to the mesh properly”, “you’re using envelopes instead of vertex groups for skinning”, and so it went on. These things were obvious to the author, since he knew exactly how the exporter worked and what assumptions it made, but to someone like me who’d never delved into the source code, all I knew was that my mesh wasn’t working properly and that I wasn’t being given any feedback to help diagnose the problem.
There’s a lesson in there: never let your software fail silently. If your user has ticked the “export animations” button, there’s a good chance that they think their mesh contains some animations. So why not check that you agree? If their mesh doesn’t contain any vertex groups that match bone names, and your code is built on the assumption that there are, it probably wouldn’t hurt to tell them.
The Mystery of Someone Else’s Code
Eventually I realised that I couldn’t rely on the author debugging my meshes for me forever, and that I was going to have to get my hands dirty to figure out why my mesh wasn’t working. I very quickly discovered what people have been telling me for years: it’s far, far harder to read code than it is to write it. I wasn’t helped by the fact that I’d never written a line of Python before in my life, nor did I have any knowledge of the Blender API. To be honest, I really rather enjoyed the challenge of figuring out this mysterious piece of code. Here are some of the tricks I used in my siege upon the citadel of mystery:
- Treat every line of code other than the one you’re actually interested in as a black box that you don’t need to understand. This was helped by the fact that the exporter was nicely broken up into lots of beautifully short functions, so to begin with I could ignore all of them except the entry point. I’ve seen plenty of people give up and go home because they wanted to understand a complex system in its entirety, and the challenge was just too great. Gradually, over time, strongholds began to fall as I captured functions into my empire of understanding
- Rewrite the code where necessary so that it documents itself. Many of the functions and variable names were either unenlightening or plain misleading. Here’s an example: the exporter gives the user two buttons, “Export All”, and “Export Selected”; which of those buttons do you think the ‘SelectObjs’ method belongs to? Well, for some unfathomable reason, that’s the function that exports all objects. Rename it! I’m a strong believer in having plenty of comments, but I’m an even bigger advocate of the idea that the code itself is the best explanation of what the code does (it’s certainly the easiest to keep up to date!) so it should be made as readable as possible by using sensible function and variable names. Got an argument called ‘obj’ which is always a ‘Mesh’ object? Rather than adding a comment to the code, why not rename it to ‘mesh_obj’ so that it comments itself? Conversely, if you have a variable called ‘mesh_obj’, make sure it doesn’t sometimes contain an ‘Armature’ object!
- Liberal use of debug output – whilst you’re in the process of understanding some code, don’t be afraid to make it output all manner of superfluous debug information to help you get a feel for what values your variables hold at different points. Similarly, it’s helpful to explicitly document what assumptions you think the code is making about the contents of its variables (in C++ I’ve started to use ‘assert’ for this a lot more than I used to). Sometimes I’ve even used this in cases where you think it can’t possibly be necessary, and unearthed some really obscure bugs – for instance, if you’re convinced (and relying upon the fact) that two expressions are equivalent (e.g. you think that parent_matrix * my_matrix = combined_matrix) then by outputting the two expressions you can get a very helpful clue when you realise that they’re actually different.
Making it your own
Having started to understand the code a bit better, I gained the confidence to start making some improvements of my own. Here are some of my favourites:
- ‘Why’ not ‘how’ – the original version of the code contained a good dozen instances of this line:
name.replace(".", "").replace(" ", "_"), to deal with the fact that object names inside a .X file can’t have dots or spaces in them. Now, in some ways this seems harmless enough, but I wanted to factor it out into a method call anyway, just because I’ve got a strong dislike for ‘copy and paste’ coding. I could have named the method, “remove_dots_and_spaces” (a ‘how it does it’ name), but I’ve come to realise that a semantically descriptive name is much more useful, so I called it “make_x_compatible_name” (a ‘why it does it’ name). By doing that, it got the creative juices flowing, and I started thinking about what else might cause a name to break your .X file, and came across an example where using a reserved word (e.g. ‘string’ or ‘integer’) as a name caused it to break. Having a method factored out made it trivial to add some code to check for reserved words, and the change then immediately applied everywhere that method was used. Fantastic!
- Pythonesque-ness – I can’t say for sure, but one got the impression reading this code that, like myself, the original author wasn’t a native Python speaker. I had great fun and learnt all sorts of neat things by rewriting things to be as Pythonesque as possible. I think so far my favourite Python feature is list comprehensions – the ability to generate new lists based upon old lists in a single line of code, a sort of combination of map and filter all in one neat piece of syntactic sugar. In my view, rewriting the code to be more ‘natural’ Python makes it a lot more compact and readable – it allows the intent to show through more clearly without being distracted by the means.
- More robust error handling – I’ve added a great deal of code to the exporter that spots problems with how your mesh is configured and reports back to you. Hopefully that means it will be a lot more useful for real life work by real life people. One of my goals was to fix anything within the exporter that would require ‘fiddling around’ by the artist, since code in the exporter only needs to be written once whereas there are a lot of 3D assets to be made, and the exporter needs to be run again and again.
You can find my version of the exporter here. If you do experience any problems with it, please report back and send me your .blend file so that I can continue to improve its error detection and feedback.