Where We Left Off Last Time…
In the first part of this how to guide, I left you with a general description of the architecture of ANSYS Mechanical product. If you haven’t read that post, I recommend that you at least skim through it to familiarize yourself with the structure of ANSYS Mechanical. One thing we established is that customizing ANSYS Mechanical is not for the faint of heart, but at the same time, it is also not an impossible task. There are two primary difficulties associated with Mechanical customization. They are:
- ANSYS Mechanical was never truly designed to be customized, at not least by end users.
- There is no documentation of the internal API, so we’re left with deciphering the inner workings ourselves.
A Recipe for Discovering ANSYS Mechanical Functionality
As I hinted in the introduction to this post, the ANSYS developers have followed a sound software development practice in the construction of the ANSYS Mechanical product. The practice is this: Find a particular pattern that solves a problem you currently face in the most general case. So, lets describe the problem the ANSYS developers faced and then we will describe what I think was their solution. The problem can be summarized as this: How do you create a GUI that is easily extended to later include functionality that you don’t even know about today? That is the problem from 50,000 ft. Here are some more details about the problem.
- Extensible means that I (the ANSYS Developer) may need to add / subtract menu items, toolbar buttons, menu groups, button groups at will as I add in more functionality over time. I don’t want to have to rewrite lots of low level code every time I need to add a button, for example.
- I already know we (ANSYS) have customers around the world who speak different languages. I don’t want to maintain 10 different versions of ANSYS Mechanical for every language that I need to support. It would be nice if I could separate the language from the rest of the code.
- If I’m going to be adding buttons, menu items, etc… at will, I need to be able to describe what happens when the user presses the button, selects the menu, etc… I don’t want to paint myself into a corner, so this needs to be as general as possible.
So, that’s the problem and perhaps you’re already seeing the solution in your head. Here is what the folks at ANSYS came up with, which is a pretty standard pattern for constructing a flexible GUI.
- Push the actual GUI construction to the very last minute. That is, you don’t hard code a GUI, rather you hard code a GUI builder engine. The GUI builder engine reads in a human readable text file at run time that describes the GUI layout and then it dynamically creates the GUI on the fly every time the program launches. Now what you have is a static executable that can dynamically morph its appearance to anything you want.
- This is a collorary to 1. Since we want to push GUI construction to the very last minute, we therefore have to have the means to describe at runtime what should happen when the user presses a button or selects a menu item in the most general terms. Hence we need a dynamic scripting language that isn’t interpreted until run time. Note that the only real purpose of this language is to orchestrate at a high level what should happen. The low level details of say meshing for example can all be constructed in a compiled language and contained in libraries as long as we can call into those libraries using this scripting language. So, because of 1 we end up with a scriptable interface to Mechanical.
- We want to isolate language differences, i.e. English, French, Germany, etc… to as small a subset of files as possible. So, the best programming technique for this problem is the old “extra level of indirection.” That is, instead of using a literal string like “File” in the code that describes the menu item “File”, we use an identifier ID_File that is just a numeric constant. Then, we construct a table that maps a string value to a numeric identifier. So, maybe ID_File has a numeric value of 438. In our table there would be an entry of: 438, “File”. Likewise, anywhere in the actual GUI that we need the string “File”, we substitute the constant ID_File in its place. Now, lets say we want a French version of ANSYS Mechanical, what do we do? We simply translate the string table entries into French and create a new string table file. At run time, we read in the French string table and voilà, we have a French version of ANSYS Mechanical.
So, that is the approach the ANSYS developers took to solve the problem of creating an easily extensible GUI. The benefit for us is that we get a scripting language to boot, and if we “run the engine backwards” we get an easy algorithm for figuring out what is going on under the hood in Mechanical. So, here is the recipe for determining functionality within ANSYS Mechanical.
- Good Text Search Tool (I use Slickedit. An added benefit is if it can search entire directories recursively)
- Locate the desired functionality in the GUI itself.
- Search the file dsstringtable.xml for the menu string, or toolbar button description string
- Note the corresponding ID_*** string ID within the string table file.
- Search for the ID_*** in dscontextmenu.xml or similar GUI structure file.
- When you find the ID_***, note the do*** command listed in the section associated with that GUI element’s action callback.
- Recursively search the aisol, or DesignSpace directory for the corresponding do*** command.
Could it possibly be simpler?
Example Use of the Above Recipe
I want to add a command object to a particular body in the tree, and eventually I want to do it programmatically.
Step 1: Locate the Functionality in the GUI Itself
Below is a screen shot of the context menu hierarchy one navigates through to insert a command object on a body in the tree. So, we see that the string we are looking for is “Commands”
Step 2: Search dsstringtable.xml for the Menu String: “Commands”
Note that the string tables are located underneath a language directory structure. If you haven’t currently guessed based on the content of this blog post, we’re using the string table located under Language\en-us\xml. When we perform the search we find the following:
Step 3: Note the Corresponding ID_*** in the String Table
You can see from the image above that the ID associated with the string Commands is ID_CommandEditorDefaultName. Sounds plausible.
Step 4: Search for ID_CommandEditorDefaultName in dscontextmenu.xml
Note, that the structure of the ANSYS Mechanical GUI is described in a series of xml files as mentioned in the sections above. The file dscontextmenu.xml is one such file that shockingly describes the layout of the various context menus within the program. So, lets search for the string ID_CommandEditorDefaultName in that file and see what we find. Here are the results of that search:
Now, one of the action callbacks is called “doPrototypeInsertCommandEditor”. If that doesn’t sound like a function that will insert a command editor on a prototype, I don’t know what does. Let’s see if we can find it.
Step 5: Search for doPrototypeInsertCommandEditor in the aisol or Designspace Directories
Well, what do you know? There is the 21 line function that gets called whenever you insert a command object onto a body in tree. In the intervening time between this post and the next, spend some time staring at this code. There are some fundamental aspects of the architecture of ANSYS Mechanical that appear even in this short function. Now, I know your probably thinking to yourself, this is all fine and dandy, but what if I don’t just want to insert a command object. What if I want the command object to have some APDL in it? Furthermore, what do all those other variables, etc… really mean. Lastly, how do I even call this blasted function? Stay tuned… there is much, much more to come.