Programming with Tcl/Tk in ANSYS

Introduction

In this article we will explore programming with tcl/tk (pronounced tickle tee-kay) in ANSYS. Tcl, an acronym that stands for Tool Command Language, is an embedded programming language that was developed by John Ousterhout in the late 1980’s while he was a professor at Berkeley. Initially, tcl grew out of a need for a simple, embeddable command language that Dr. Ousterhout and his colleagues could reuse within different integrated circuit design tools that they were developing at the time [1]. In a recent article, Dr. Ousterhout outlined the requirements he had in mind when he created tcl.

“The notion of embeddability is one of the most unique aspects of Tcl, and it led me to the following three overall goals for the language:

Shortly after developing tcl, Dr. Ousterhout’s interests shifted from integrated circuit design tools to graphical user interfaces (GUIs). At this time, again in the late 1980’s, GUIs were still very much in their infancy. Furthermore, the manpower needed at that time to develop a usable GUI was rather substantial. Driven by a fear that his research team would be under staffed to tackle complex GUI development projects, Dr. Ousterhout realized that substantial interactive systems could be built out of smaller reusable components, which were then programmatically “glued” together. It was clear to Dr. Ousterhout that the tcl language would be ideal as the “programmatic glue” for such interactive systems, if only there existed a set of GUI components from which to build a system. Consequently, he begin developing tk in order to fulfill the need for a toolkit of reusable GUI components.

Tcl/tk began to gain popularity throughout the software development community primarily via word-of-mouth. Today it is widely used throughout a plethora of industries largely because the source code for the tcl interpreter is freely available for download from the internet [2].

Furthermore, the 5.7 release of ANSYS marked a substantial advancement of tcl within ANSYS. Within the 5.7 release, a fully functional tcl interpreter was exposed via the ~eui external command. Each subsequent release of ANSYS has continued to add functionality based on tcl/tk, culminating in the ANSYS 6.1 release in which the new GUI is completely implemented with tcl/tk.

The remainder of this article focuses on programming with tcl/tk in ANSYS. We will first briefly look at the tcl language and the tk toolkit in a generic sense. Then we will explore ways in which programs written in tcl can be written to interface directly with ANSYS. Finally, we will wrap up some loose ends and discuss a few of the more advanced aspects of tcl that help mitigate complexity in large projects.

The Tcl/Tk Programming Language

Tcl is an interpreted command language, and as such, requires an external piece of software known as the interpreter, which executes a tcl program stored in a text file. Outside of ANSYS, two interpreters are widely used: tclsh and wish. Tclsh simply implements the core tcl language, whereas wish extends the tclsh interpreter by natively exposing the tk toolkit. Therefore, GUI applications written outside of ANSYS should be executed using the wish interpreter. Since wish is a superset of tclsh, it can be used exclusively if desired. Inside of ANSYS, the equivalent of a wish interpreter is provided by the ~eui external command. Since this article focuses on tcl/tk within ANSYS, we will execute all of the example programs from within ANSYS via the ~eui command. However, I have often found that I am more productive initially if I begin developing a new application outside of ANSYS using an off the shelf tcl interpreter [2]. Only after the application is functioning correctly do I typically move into ANSYS.

Let’s begin with the prototypical example application used to introduce all modern programming languages; the “Hello World” app. Please follow the steps below to create this application.

  1. Launch ANSYS and note the working directory.
  2. Launch you favorite text editor and type in the listing shown in Figure 1. Tcl is case sensitive and somewhat non-freeform. Therefore it is imperative that you type the program in exactly as it is listed in Figure 1.
  3. Save this file in the current ANSYS working directory with the name hello1.tcl
  4. Execute this script in ANSYS using the ANSYS command listed at the bottom of Figure 2.

proc HelloWorld1 {} {
     puts “Hello World!”
}

HelloWorld1

Figure 1. First "Hello World" application.

~eui, ‘source [file join [pwd] hello1.tcl]’

Figure 2. Command to execute a tcl script within ANSYS that is located in the current working directory.

Follow these steps and you should see the string “Hello World!” printed in the ANSYS output window.

As you can see, tcl has a syntax that is very similar to the “C” programming language. It is a structured, procedural language that has a rich set of control structures. The first line in our example program declares a procedure named “HelloWorld1”. Procedures in tcl are analogous to functions in “C” or subroutines in FORTRAN. The second line calls a built-in tcl procedure named “puts” which prints a string to the standard output. The third line contains the closing brace for the procedure, and the final line simply calls the procedure named HelloWorld1. You will notice that there is no main function. In fact, execution in tcl begins with the first executable statement, which in this case is the last line. Consequently, this program could have simply been written using a single line: puts “Hello World!”

The fundamental data type in tcl is the string. Consequently, all variables used in a tcl program are represented within the tcl interpreter as strings. This provides a great deal of flexibility at a small cost in performance. Remember, however, tcl is not intended to be a computational workhorse, but rather a glue language, so the performance hit for typical glue code should be negligible. The second most common data type in tcl is the list. Internally, a list is stored within tcl as a special kind of string. A list allows you to effortlessly manipulate a collection of data. Before we move to the next example there is one last concept regarding variables that needs to be mentioned, that is the concept of scope. Variables within tcl can live within one of two scopes: the local or global scope. (Tcl namespaces add an additional scope, however, namespaces will not be discussed in this article.) The following example demonstrates the concept of local and global variables as well as lists and function arguments.

proc Hello2 {People} {
     global Salutation
     foreach Person $People {
          puts “Hello $Person”
     }
     set NumberOfPeople [llength $People]
     if { $NumberOfPeople > 1 } {
          puts “I said hello to $NumberOfPeople people.”
     } else {
          puts “I said hello to one person.”
     }
     puts $Salutation
}
set Salutation “Good-bye!”
Hello2 [list Fred Jane Bob]

Figure 3. Second "Hello" application.

To execute the code in Figure 3, follow the same steps mentioned above. However, save the code in Figure 3 with the name hello2.tcl. Substitute the file name hello2.tcl for hello1.tcl in the command given in Figure 2. You should see the following output in the ANSYS output window:

Hello Fred
Hello Jane
Hello Bob
I said hello to 3 people.
Good-bye!

Figure 4. Output from hello2.tcl.

The example program in Figure 3 is somewhat more substantial, but it succinctly demonstrates many of the features of tcl. Most of the programmatic constructs should be familiar except perhaps the foreach construct. The foreach construct is used to iterate over the contents of a tcl list. The list is passed into the function Hello2 via the People variable. The global variable, Salutation is set to “Good-bye!” outside of the procedure Hello2 on the second to last line.

In the next example, we will use the tk toolkit to create a simple GUI. The GUI will consist of a single button that, when pressed, will invoke the Hello2 procedure shown in Figure 3. Figure 4 shows the code.

destroy .tplvl
set t [toplevel .tplvl]
set b1 [button .tplvl.b1 –command “Hello2 Jim” \
     –text “Say Hello” ]

pack $b1
proc Hello2 {People} {
     global Salutation
     foreach Person $People {
          puts “Hello $Person”
     }
     set NumberOfPeople [llength $People]
     if { $NumberOfPeople > 1 } {
          puts “I said hello to $NumberOfPeople people.”
     } else {
          puts “I said hello to one person.”
     }
     puts $Salutation
}
set Salutation “Good-bye!”

Figure 5. Second "Hello" application.

Again, to execute this code, follow the procedure outlined above. However, save this file as hello3.tcl and change the ~eui command listed in Figure 2 accordingly. You should see the following window on your screen.


Figure 6. Tk window for the example in Figure 5.

Let’s analyze the first five lines in Figure 5 since they are different from the code in Figure 4. The first two lines are necessary when programming in tcl/tk within ANSYS. The first line destroys any previous instance of the window shown in Figure 6. If no instance of this window exists, then the first line simply returns. The second line actually creates a new toplevel window into which we can add additional tk widgets to form the GUI for our application. We are implicitly storing the returned value from the toplevel call into the variable t.

The tk toolkit uses an implicit hierarchical syntax to impose parent-child relationships between widgets. The root window of any tk application is referenced in the code as the “.” window. All other windows are children on the root window “.”. So, in Figure 5 on line two, we are creating a new toplevel window named “tplvl” whose parent is the root window “.”. This relationship is encoded in the name of the window, which is “.tplvl”. The encoding is analogous to the file structure used on most modern operating systems, especially UNIX. The separator for encoding the parent child relationship is the “.”.

On the third and fourth lines we are creating a button that will be a child of the new toplevel window we just created. This is accomplished by encoding the parent child relationship into the name of the button. The name of the button is given as the first argument to the button command, which is “.tplvl.b1”. So, following the naming convention used by tk, we see that the button, b1, is a child of the toplevel window, tplvl, who is a child of the root window “.” This parent-child relationship insures that when the window shown in Figure 6. is created by the tk toolkit, the button will appear inside the toplevel window.

When you press the button, “Say Hello”, the tk toolkit will translate that event into a call to the procedure Hello2. This link between pressing a button and executing some tcl code is created via the –command argument to the button function. As you can see on the third line in Figure 5, the –command argument is followed by a string. This string contains the name of a procedure, Hello2, followed by arguments to that procedure. This creates the implicit link between the user’s action of pressing the button and a command that is executed.

The fifth line in figure 5 contains the command pack $b1. As mentioned above, this command is responsible for placing the button onto the toplevel window. The pack command in tk works much like packing a box with smaller rectangular items. Once all the items are packed, and just prior to displaying the window, the tk toolkit shrinks the outer box, in this case the toplevel window, around the smaller boxes so that all of the slack space is taken up. The packing mechanism is a powerful tool for placing widgets on a window because it allows the tk framework to reposition widgets on the screen as a window is resized.

The tk toolkit provides two additional mechanisms for placing widgets on the screen. They are the grid command and the place command. The grid command is similar in power to the pack command. It differs slightly in methodology in that it allows you to place widgets into cells within a virtual grid that overlays a window. A powerful feature of the tk toolkit is that both the pack and grid commands can be use in conjunction within the same window. Furthermore, by exploiting the hierarchical nature of tk, along with certain grouping widgets like the frame widget, extremely complex user interfaces can be constructed. Though a detailed discussion of such techniques is beyond the scope of this article, additional information on these commands can be found in references 3, 4 and 5.

The tk toolkit has a substantial assortment of standard widgets. In addition, several free add-on packages such as Bwidgets, Iwidgets, incr tcl, etc… exist which provide higher level GUI widgets. A list of the standard widgets is shown in Table 1.

Widget Description
button Creates a standard pushbutton
canvas Creates a canvas on which graphic primitives can be drawn
checkbutton Creates a standard check button
entry Creates a single line text entry box
frame Creates a virtual frame useful for grouping widgets
label Creates a static text label
listbox Creates a standard list box
menu Creates either a popup menu or a standard application menu
menubutton Creates a menu item with an associated on/off state
message Creates a small message window
radiobutton Creates a standard exclusive radio button
scale Creates a sliding scale control
scrollbar Creates a standard scroll bar
text Creates a multi-line text widget with extensive formatting capabilities
toplevel Creates a new toplevel window into which widgets can be placed

Table 1. Standard tk widgets.

Programming Tcl/tk in ANSYS “Just think APDL”

Now that we have introduced tcl/tk in a generic sense, we next move to writing a small tcl/tk application that interacts directly with ANSYS. This application will simply create a node at a given location in the x-y plane. As you will see, interacting with ANSYS through tcl is extremely straightforward. In a nutshell, you simply construct APDL commands as strings within tcl and then send them to ANSYS. Consider the code shown in Figure 7.

destroy .tplvl
set t [toplevel .tplvl]
wm title $t “Create Node”
set xLab [label $t.lx –text “X:”]
set yLab [label $t.ly –text “Y:”]
set xEntry [entry $t.ex –textvariable x]
set yEntry [entry $t.ey –textvariable y]
set b1 [button $t.b1 –command “CreateNode” \
          –text “Create Node” ]
grid $xLab –row 0 –column 0
grid $xEntry –row 0 –column 1
grid $yLab –row 1 –column 0
grid $yEntry –row 1 –column 1
grid $b1 –row 2 –column 0 –columnspan 2

proc CreateNode {} {
     global x y
     set NextNode [ans_getvalue “NODE,0,NUM,MAXD”]
     ans_sendcommand “/prep7”
     ans_sendcommand “n,$NextNode,$x,$y”
}

Figure 7. Application to create a node in ANSYS.

To execute the code shown in Figure 7, enter the text into a text file named createnode.tcl and save this file in the current ANSYS working directory. Next issue the command shown in Figure 2 substituting createnode.tcl for hello1.tcl. When this code is executed a window resembling the one shown in Figure 8 should appear.


Figure 8. Create node dialog.

The interesting code in this example is contained within the CreateNode procedure. All of the code above the CreateNode procedure is responsible for creating and displaying the various widgets within the dialog box. Please refer to the previous section to gain an overview of what this code does. [3,4,5]

The CreateNode procedure is called whenever the button “Create Node” is pressed. The first line within this procedure declares two variables, x and y, as global variables. These variables are attached to the two entry boxes adjacent to the “X:” and “Y:” labels. Whenever a user enters a number into one of the text boxes, the tk toolkit updates the corresponding variable to contain the value entered by the user. By declaring these variables as global variables in the procedure CreateNode, we gain access to the values that the user typed into the two entry boxes.

The next line in the CreateNode procedure creates a local variable named NextNode. This variable stores the result from a call to a procedure named ans_getvalue. The ans_getvalue procedure is the first of the two primary procedures used to communicate with ANSYS via tcl. This procedure mimics the *GET APDL command. In fact, the single argument to the ans_getvalue procedure is a string containing fields 3 through 8 of the *GET command in comma delimited format. Therefore, any parameter from an ANSYS database that is accessible via the *GET APDL command can also be accessed and stored in a tcl variable via the ans_getvalue procedure. So, by interpreting the argument as a *GET command, we see that we are simply asking ANSYS to return the next higher node number not yet defined in the database. We will then use this number to create a unique node at a given position.

The next two lines in the CreateNode procedure are calls to a function named ans_sendcommand. This is the second of the two primary procedures used to communicate with ANSYS. As mentioned above, the first procedure, ans_getvalue, is used to extract data from the ANSYS database. The ans_sendcommand procedure, however, is the compliment of the ans_getvalue procedure. This procedure is used to send specific APDL commands to ANSYS. The single argument to this function is a string comprising a valid APDL command. As you can see, the first call to ans_sendcommand sends the /prep7 command to ANSYS to force ANSYS to enter the preprocessor. The second call to the ans_sendcommand is more interesting. This call constructs, via variable substitution, an ANSYS command to create a node. As you can see, we build up the command by substituting the value of the NextNode variable into the second field of the ANSYS N command, and then the value of the x and y variables into the subsequent fields of the N command.

So, as an example, lets say there are no nodes currently defined in the database. The user types 0.5 into the “X:” entry and 1.5 into the “Y:” entry and then presses the “Create Node” button. This causes the CreateNode procedure to be called. When the call to ans_getvalue is executed, a return value of 1 is stored in the NextNode variable since no nodes are currently defined in the database. The tk toolkit has already stored 0.5 in the “x” variable and 1.5 in “y” variable. Since we declared these variables as global, we have access to their values within the CreateNode procedure. So, when the second call to ans_sendcommand is executed, the tcl interpreter substitutes the values for these three variables into the procedure argument before passing control to the ans_sendcommand. This variable substitution results in a string equal to “n,1,0.5,1.5”. The ans_sendcommand then takes this string and feeds it to the ANSYS APDL command interpreter. Finally, the command interpreter executes this command just as if you had typed it into the command window. The result then is a new node at the location specified by the user.

Conclusion

We’ve covered quite a bit of information in this article culminating in a description of how the tcl programming language interfaces with ANSYS. However, the key idea when programming in tcl for ANSYS was shown to be quite simple; that is “Just think APDL”. By using a combination of the ans_getvalue and ans_sendcommand procedures along with the tk toolkit, complex, highly interactive, and visually pleasing applications can be created within ANSYS that customize its functionality for a given process. Though we have covered a substantial amount of territory in this article, we’ve really only highlighted a few aspects of programming in tcl. Hopefully, this introduction has successfully whetted you’re appetite for programming in tcl within ANSYS. If so, I suggest that you peruse through the references sited at the end of this article to become more grounded in tcl/tk programming.

References

[1] www.tcl.tk/doc/tclHistory.html
[2] www.activestate.com/Solutions/Programmer/Tcl.plex
[3] Raines, P. & J. Tranter, Tcl/Tk in a Nutshell, O’Reilly, 1999
[4] Harrison, M. & M. McLennan, Effective Tcl/Tk Programming, Addison-Wesley, 1998.
[5] Foster-Johnson, E., Graphical Applications with Tcl/Tk, M&T Books, 2/ed, 1997.