Friday, June 12, 2009

LabVIEW Scripting Tip #3: How to Handle References

Once you get past the really simple scripting apps, you'll quickly discover that your scripting VIs can become unwieldy at a moment's notice, and this is primarily due to how many VI Server references you have to deal with in a complex scripting app. Here now is a list of things to keep in mind when dealing with a plethora of scripting references all over your diagram.
  1. When to Close References - For every single VI Server reference type *except* VI and Application, it's fine to close the reference as soon as you're done with it. I've seen people write scripting VIs before where they thought they needed to keep each "parent" reference open as long as they were using a child reference. This is not the case. Check out this sample diagram, which is getting the first tunnel it finds on a Case Structure and determining if it is an output or an input tunnel:







    This code will work, but those wires don't need to span the entire diagram just to be closed later. You can imagine these wires could get very long as the complexity of your scripting code increases. Instead, take an approach like this:







    Here, we are closing the references as soon as we're done with them. The diagram looks cleaner, and we don't have to worry about passing really long wires around. Also, note the trick I'm doing with the array of Tunnel references. Often in VI Scripting, you'll have an array of references, but you're only interested in one of those references. In these cases, you can use the Delete From Array function to pull out the one you're interested in, and close all the rest.
  2. Comparing References - If you are trying to compare two references to see if they refer to the same object, you can simply use the Equal? function. Similarly, if you have an array of references, and you want to search that array to find the index of a particular reference, you can use the Search 1D Array function. Even though two references to the same object may have different flattened U32 refnum values (as can pretty easily be seen with the Type Cast function), using the comparison functions in LabVIEW will compare the actual objects those references refer to, and not the numeric refnum values, when performing the comparison.
  3. Checking Reference Type - Many times you'll have a reference to an object, and you'll want to figure out what type of object it is. You may be inclined to read the Class Name property of that object, but this approach can cause problems. For example, let's say you have a reference to a Node, and you want to see if it that Node is a member of the Structure class (Case Structure, While Loop, For Loop, etc.). So you read the Class Name property of the reference and see if it equals "Structure". Well, this approach will never work, because the Class Name property always returns the most specific class name of the object...so it would return CaseStructure, WhileLoop, ForLoop, etc. Instead of using the Class Name approach, you can use the To More Specific Class function and check the error output:






    In this case, as long as the Node is any type of Structure, the class conversion will succeed.
Does anybody have any additional tips for dealing with references when programming VI Scripting applications? Let's hear them!

Friday, June 5, 2009

LabVIEW Scripting Tip #2: Supporting Undo

One of the great things about LabVIEW Scripting is that we can implement tools that perform actions in the LabVIEW editor. There are already several utilities posted on LAVA along these lines, along with the hopefully-soon-to-be-released JKI Right-Click Framework. Not to mention current and future shipping LabVIEW features.

In order to give your scripting-based utility equal footing with built-in LabVIEW features, though, you need to make sure anything you do with scripting can be undone or redone by the user. Thankfully, there is support in scripting for setting up Undo/Redo transactions, as illustrated by this screenshot:



Here are some details on each of the methods shown in the screenshot:
  • Transaction.Begin Undo - Call this method before you start your scripting operation. The "Name" input is the text that you want users to see next to the Undo/Redo options in the Edit menu:











    After calling Begin Undo, you will do all the scripting code you need to do for whatever editor operation you are performing. While doing your scripting, you need to keep track of a boolean, or some sort of flag, indicating whether or not you actually want to complete the Undo transaction, so you can make a decision between the following two methods.
  • Transaction.End Undo - Did everything script ok? Assuming you didn't get any errors, and that your scripting code actually did change something on the target VI, you need to end your Undo transaction. This actually commits the scripting action to the target VI, and creates the Edit > Undo option in the menu with the Name you specified on Begin Undo.
  • Transaction.Fail - Did an error occur during your scripting? Or did your scripting code not actually change anything? If either of these is the case, you probably want to Fail the transaction. This means that the transaction is not committed to the target VI, and that no new Undo option is added to the Edit menu. Notice that in my screenshot, I do not wire the error up to the Transaction.Fail method. This is because, if an error occurred in my scripting, I still want the Fail method to execute so it cancels the transaction.
If you decide not to wrap your scripting operations with these transaction methods, there are two significant consequences. First, and most obvious, is that users will not be able to undo whatever scripting operation you have performed. Second, and more severe, is the fact that a scripting operation that is not wrapped with these transaction methods can clear the existing Undo stack of a VI. So if you are developing a first-class scripting utility with any sort of integration into the LabVIEW editor, make sure to take these simple steps to make your operations undoable/redoable.

Wednesday, June 3, 2009

LabVIEW Scripting Tip #1: The Power of Traverse

This blog post is the first in an as yet unknown number of posts detailing some useful tips I have on LabVIEW Scripting.

If you're ever examining VIs to find objects of a particular type, then you definitely want to check out the following utility VI:

[LabVIEW]\vi.lib\utility\traverseref.llb\TRef Traverse for References.vi

This VI will return references to all the objects of a given class that reside within the panel, diagram, or a user-defined object in a VI. The traversal is recursive, so it will find objects nested within other objects. Here's a simple example that finds all the Case Structures on a diagram:









We specify a target of "BD" (for Block Diagram) and a class name of "CaseStructure", and we're good to go. Note that you can also traverse the front panel for objects by specifying a target of "FP". And if you want to traverse a specific object (i.e. find all the clusters that reside within a particular Tab Control on the front panel), then specify a target of "Other" and pass in a reference to the Tab Control to the "Other Refnum" input.

You may wonder why I recommend using this VI, when there is already a scripting property called AllObjects[] for the Diagram class (and a similar property for the panel). Well, that AllObjects[] property does not recurse into container objects. So to completely search a diagram for objects, you would need to get the AllObjects[] property, figure out which objects are containers, separate those objects out, get the container diagrams, do an AllObjects[] on them, etc. etc. This is certainly doable, and I'm guessing people have already written VIs that do this...but in my experience, using the Traverse VI is much, much faster than trying to recurse the entire diagram with AllObjects[].

This traverse VI is used extensively in the VI Analyzer Toolkit, since we are often looking for specific objects on the diagram that we need to examine. And if you know the label of a specific object you are looking for, then you can use the TRef Find Object By Label.vi in that same LLB, which is basically a wrapper for TRef Traverse for References.vi, but it goes an extra step and reads the labels of each found object until it finds the one you were looking for.

Just to give you an idea how useful this VI can be, TRef Traverse for References.vi originally shipped only with the VI Analyzer Toolkit 1.0 when LabVIEW 7.0 released. However, once it got to the point that about a dozen internal groups at NI had asked me for a copy of the Traverse VI so they could ship it with their products, I decided it would be best for all of us if a single copy of the VI shipped with core LabVIEW, which it has since LabVIEW 8.0.