Experiences with Developing a “Somewhat Large” ACT Extension in ANSYS

With each release of ANSYS the customization toolkit continues to evolve and grow.  Recently I developed what I would categorize as a decent sized ACT extension.    My purpose in this post is to highlight a few of the techniques and best practices that I learned along the way.

Why I chose C#?

Most ACT extensions are written in Python.  Python is a wonderfully useful language for quickly prototyping and building applications, frankly of all shapes and sizes.  Its weaker type system, plethora of libraries, large ecosystem and native support directly within the ACT console make it a natural choice for most ACT work.  So, why choose to move to C#?

The primary reasons I chose to use C# instead of python for my ACT work were the following:

  1. I prefer the slightly stronger type safety afforded by the more strongly typed language. Having a definitive compilation step forces me to show my code first to a compiler.  Only if and when the compiler can generate an assembly for my source do I get to move to the next step of trying to run/debug.  Bugs caught at compile time are the cheapest and generally easiest bugs to fix.  And, by definition, they are the most likely to be fixed.  (You’re stuck until you do…)
  2. The C# development experience is deeply integrated into the Visual Studio developer tool. This affords not only a great editor in which to write the code, but more importantly perhaps the world’s best debugger to figure out when and how things went wrong.   While it is possible to both edit and debug python code in Visual Studio, the C# experience is vastly superior.

The Cost of Doing ACT Business in C#

Unfortunately, writing an ACT extension in C# does incur some development cost in terms setting up the development environment to support the work.  When writing an extension solely in Python you really only need a decent text editor.  Once you setup your ACT extension according to the documented directory structure protocol, you can just edit the python script files directly within that directory structure.  If you recall, ACT requires an XML file to define the extension and then a directory with the same name that contains all of the assets defining the extension like scripts, images, etc…  This “defines” the extension.

When it comes to laying out the requisite ACT extension directory structure on disk, C# complicates things a bit.  As mentioned earlier, C# involves a compilation step that produces a DLL.  This DLL must then somehow be loaded into Mechanical to be used within the extension.  To complicate things a little further, Visual Studio uses a predefined project directory structure that places the build products (DLLs, etc…) within specific directories of the project depending on what type of build you are performing.   Therefore the compiled DLL may end up in any number of different directories depending on how you decide to build the project.  Finally, I have found that the debugging experience within Visual Studio is best served by leaving the DLL located precisely wherever Visual Studio created it.

Here is a summary list of the requirements/problems I encountered when building an ACT extension using C#

  1. I need to somehow load the produced DLL into Mechanical so my extension can use it.
  2. The DLL that is produced during compilation may end up in any number of different directories on disk.
  3. An ACT Extension must conform to a predefined structural layout on the filesystem. This layout does not map cleanly to the Visual studio project layout.
  4. The debugging experience in Visual Studio is best served by leaving the produced DLL exactly where Visual Studio left it.

The solution that I came up with to solve these problems was twofold.

First, the issue of loading the proper DLL into Mechanical was solved by using a combination of environment variables on my development machine in conjunction with some Python programming within the ACT main python script.  Yes, even though the bulk of the extension is written in C#, there is still a python script to sort of boot-load the extension into Mechanical.  More on that below.

Second, I decided to completely rebuild the ACT extension directory structure on my local filesystem every time I built the project in C#.  To accomplish this, I created in visual studio what are known as post-build events that allow you to specify an action to occur automatically after the project is successfully built.  This action can be quite generic.  In my case, the “action” was to locally run a python script and provide it with a few arguments on the command line.  More on that below.

Loading the Proper DLL into Mechanical

As I mentioned above, even an ACT extension written in C# requires a bit of Python code to bootstrap it into Mechanical.  It is within this bit of Python that I chose to tackle the problem of deciding which dll to actually load.  The code I came up with looks like the following:

Essentially what I am doing above is querying for the presence of a particular environment variable that is on my machine.  (The assumption is that it wouldn’t randomly show up on end user’s machine…) If that variable is found and its value is 1, then I determine whether or not to load a debug or release version of the DLL depending on the type of build.  I use two additional environment variables to specify where the debug and release directories for my Visual Studio project exist.  Finally, if I determine that I’m running on a user’s machine, I simply look for the DLL in the proper location within the extension directory.  Setting up my python script in this way enables me to forget about having to edit it once I’m ready to share my extension with someone else.  It just works.

Rebuilding the ACT Extension Directory Structure

The final piece of the puzzle involves rebuilding the ACT extension directory structure upon the completion of a successful build.  I do this for a few different reasons.

  1. I always want to have a pristine copy of my extension laid out on disk in a manner that could be easily shared with others.
  2. I like to store all of the various extension assets, like images, XML files, python files, etc… within the Visual Studio Project. In this way, I can force the project to be out of date and in need of a rebuild if any of these files change.  I find this particularly useful for working with the XML definition file for the extension.
  3. Having all of these files within the Visual Studio Project makes tracking thing within a version control system like SVN or git much easier.

As I mentioned before, to accomplish this task I use a combination of local python scripting and post build events in Visual Studio.  I won’t show the entire python code, but essentially what it does is programmatically work through my local file system where the C# code is built and extract all of the files needed to form the ACT extension.  It then deletes any old extension files that might exist from a previous build and lays down a completely new ACT extension directory structure in the specified location.  The definition of the post build event is specified within the project settings in Visual Studio as follows:

As you can see, all I do is call out to the system python interpreter and pass it a script with some arguments.  Visual Studio provides a great number of predefined variables that you can use to build up the command line for your script.  So, for example, I pass in a string that specifies what type of build I am currently performing, either “Debug” or “Release”.  Other strings are passed in to represent directories, etc…

The Synergies of Using Both Approaches

Finally, I will conclude with a note on the synergies you can achieve by using both of the approaches mentioned above.  One of the final enhancements I made to my post build script was to allow it to “edit” some of the text based assets that are used to define the ACT extension.  A text based asset is something like an XML file or python script.  What I came to realize is that certain aspects of the XML file that define the extension need to be different depending upon whether or not I wish to debug the extension locally or release the extension for an end user to consume.  Since I didn’t want to have to remember to make those modifications before I “released” the extension for someone else to use, I decided to encode those modifications into my post build script.  If the post build script was run after a “debug” build, I coded it to configure the extension for optimal debugging on my local machine.  However, if I built a “release” version of the extension, the post build script would slightly alter the XML definition file and the main python file to make it more suitable for running on an end user machine.   By automating it in this way, I could easily build for either scenario and confidently know that the resulting extension would be optimally configured for the particular end use.

Conclusions

Now that I have some experience in writing ACT extensions in C# I must honestly say that I prefer it over Python.  Much of the “extra plumbing” that one must invest in in order to get a C# extension up and running can be automated using the techniques described within this post.  After the requisite automation is setup, the development process is really straightforward.  From that point onward, the increased debugging fidelity, added type safety and familiarity a C based language make the development experience that much better!  Also, there are some cool things you can do in C# that I’m not 100% sure you can accomplish in Python alone.  More on that in later posts!

If you have ideas for an ACT extension to better serve your business needs and would like to speak with someone who has developed some extensions, please drop us a line.  We’d be happy to help out however we can!

 

Programming a Simple Polygon Editor

polygon-editor-icon-1Part of my job at PADT is writing custom software for our various clients.  We focus primarily on developing technical software for the engineering community, with a particular emphasis on tools that integrate with the ANSYS suite of simulation tools.  Frankly, writing software is my favorite thing to do at PADT, simply because software development is all about problem solving.

This morning I got to work on a fairly simple feature of a much larger tool that I am currently developing.  The feature I’m working on involves graphically editing polygons.  Why, you ask am I doing this?  Well, that I can’t say, but nonetheless I can share a particularly interesting problem (to me at least) that I got to take a swing at solving.  The problem is this:

When a user is editing a node in the polygon by dragging it around on the screen, how do you handle the case when they drop it on an existing node?

Consider this polygon I sketched out in a prototype of the tool.

polygon-editor-f01

What should happen if the user drags this node over on top of that node:polygon-editor-f02

Well, I think the most logical thing to do is that you merge the two nodes together.  Implementing that is pretty easy.  The slightly harder question is what to do with the remaining structure of the polygon?  For my use case, polygons have to be manifold in that no vertex is connected to more than two edges. (The polygons can be open and thus have two end vertices connected to only one edge.)  So, what part do you delete?  Well, my solution is that you delete the “smaller” part, where “smaller” is defined as the part that has the fewest nodes.  So, for example, this is what my polygon looks like after the “drop”

polygon-editor-f03Conceptually, this sounds pretty simple, but how do you do it programmatically?  To give some background, note that the nodes in my polygon class are stored in a simple, ordered C++ std::list<>.

Now, I use a std::list<> simply because I know I’m going to be inserting and deleting nodes at random places.  Linked lists are great for that, and for rendering, I have to walk the whole list anyway, so there’s no performance hit there.  Graphically, my data structure looks
something like this:polygon-editor-f04Pretty simple.  For a closed polygon, my class maintains a flag and simply draws an edge from the last node to the first node.

The rub comes when you start to realize that there are tons of different ways a user might try to merge nodes together in either an open or closed polygon.  I’ve illustrated a few below along with what nodes would need to be merged in the corresponding data structure.  In the data structure pictures, the red node is the target (the node on which the user will be dropping) and the green node is the one they are manipulating (the source node).

Here is one example:

polygon-editor-f05polygon-editor-f06

Here is another example:

polygon-editor-f07
polygon-editor-f09b

Finally, here is one more:polygon-editor-f08polygon-editor-f09

In all these examples, we have different “cases” that we need to handle.  For instance, in the first example the portion of the data structure we want to keep is the stuff between the source and target nodes.  So, the stuff on the “ends” of the list needs to be deleted.  In the middle case, we just need to merge the source and target together.  Finally, in the last case, the nodes between the source and target need to be deleted, whereas the stuff at the “ends” of the list need to be kept.

This simple type of problem causes shivers in many programmers, and I’ll admit, I was nervous at first that this problem was going to lead to a solution that handled each individual case respectively.  Nothing in all of programming is more hideous than that.  So, there has to be a simple way to figure out what part of the list to keep, and what part of the list to throw away.

Now, I’m sure this problem has been solved numerous times before, but I wanted to take a shot at it without googling.  (I still haven’t googled, yet… so if this is similar to any other approach, they get the credit and I just reinvented the wheel…)  I remember a long time ago listening to a C++ programmer espouse the wonders of the standard library’s algorithm section.  I vaguely remember him droning on about how wonderful the std::rotate algorithm is.  At the time, I didn’t see what all the fuss was about.  Now, I’m right there with him.  std::rotate is pretty awesome!

std::rotate is a simple algorithm.  Essentially what it does is it takes the first element in a list, pops it off the list and moves it to the rear of the list.  Everything else in the list shifts up one spot.  This is called a left rotate, because you can imagine the items in the list rotating to the left until they get to the front of the line, at which point they fall off and are put back on the end of the list.  (Using reverse iterators you can effectively perform a right rotate as well.)  So, how can we take advantage of this to simplify figuring out what needs to be deleted from our list of nodes?

Well, the answer is remarkably simple.  Once we locate the source and target nodes in the list, regardless of their relative position with respect to one another or to the ends of the list, we simply left rotate the list until the target becomes the head of the list.  That is, if we start with this:polygon-editor-f10We left rotate until we have this:polygon-editor-f11That’s great, but what does that buy us?  Well, now that one of the participating nodes is at the head of the list, our problem is much simpler because all of the nodes that we need to delete are now at either end of the list.  The only question left to answer is which end of the list do we trim off?  The answer to that question is trivial.  We simply trim off the shorter end of the list with respect to the source node (the green node in the diagram). The “lengths” of the two lists are defined as follows.  For the head section, it’s the number of nodes up to, but not including the source. (This section obviously includes the target node)  For the tail, it’s the number of nodes from the source to the end, including the source.  (This section includes the source node).  Since we define the two sections this way we are guaranteed to delete either the source or the target, but not both.  Its fine to delete either one of them, because at this point we’ve deemed the geometrically coincident, but we must not accidentally delete both!!

In the example just given, after the rotate, we would delete the head of the list.  However, let’s take a look at our first example.  Here is the original list:

polygon-editor-f12Here is the rotated list:polygon-editor-f13So, in this case, the “end” of the list (including the source) is the shortest.  If it is a tie, then it doesn’t matter, just pick one.  Interestingly enough, if the two nodes are adjacent in the original list, then the rotated list will look like either this:polygon-editor-f14 Or this, if the source is “before” the target in the original list:polygon-editor-f15In either case, the algorithm works unchanged, and we only delete one node.  It’s beautiful! (At least in my opinion…)  Modern C++ makes this type of code really clean and easy to write.  Here is the entire thing, including the search to located geometrically adjacent nodes as well as the merge. The standard library algorithms really help out!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Search lambda function for looking for any other node in the list that is
// coindicent to this node, except this node.
auto searchAdjacentFun = [this, pNode](const NodeListTool::AdjustNodePtrT &amp;pOtherNode)-&gt;bool
{
if (pNode-&gt;tag() == pOtherNode-&gt;tag()) return false;
return (QVector2D(pNode-&gt;pos() - pOtherNode-&gt;pos()).length() &lt; m_snapTolerance); }; auto targetLoc = std::find_if(m_nodes.begin(), m_nodes.end(), searchAdjacentFun); // If we don't find an adjacent node within the tolerance, then we can't merge if (targetLoc == m_nodes.end()) { return false; } // Tidy things up so that the source has exactly the same position as the target pNode-&gt;setPos((*targetLoc)-&gt;pos());
 
// Begin the merge by left rotating the target so that it is at the
// beginning of the list
std::rotate(m_nodes.begin(), targetLoc, m_nodes.end());
 
// Find this node in the list
auto searchThis = [this, pNode](const NodeListTool::AdjustNodePtrT &amp;pOtherNode)-&gt;bool
{
return (pNode-&gt;tag() == pOtherNode-&gt;tag());
};
auto sourceLoc = std::find_if(m_nodes.begin(), m_nodes.end(), searchThis);
 
// Now, figure out which nodes we are going to delete.
auto distToBeg = std::distance(m_nodes.begin(), sourceLoc);
auto distToEnd = std::distance(sourceLoc, m_nodes.end());
 
if (distToBeg &lt; distToEnd) { // If our source is closer to the beginning (which is the target) // than it is to the end of the list, then we need to delete // the nodes at the front of the list m_nodes.erase(m_nodes.begin(), sourceLoc); } else { // Otherwise, delete the nodes at the end of the list m_nodes.erase(sourceLoc, m_nodes.end()); } // Now, see if we still have more than 2 vertices if (m_nodes.size() &gt; 2) {
m_bClosed = true;
}
else {
m_bClosed = false;
}
return true;