Simulation Aids Module by Automation Professionals
E-Mail Support

Advanced / objectScript

An alternative to runScript that packages any number of additional function arguments as an "args" tuple for use within the script expression. In client or designer scopes, provides access to the binding properties. In all scopes, provides a "state" dictionary unique to the expression instance to carry information from one execution to the next.

Syntax

objectScript(pythonExpression, args...) returns Object

ArgumentData TypeDescription
pythonExpressionStringPython one-line expression to evaluate with the supplied "args" tuple, supplied "state" dictionary, and possibly with a "binding" object.
argsObject[]Zero or more arguments to be packed into a python "args" tuple, supplying varying values to the expression without requiring a recompile, and without having to convert to a string.

Usage Notes

The built-in runScript function has some deficiencies that limit its use in a number of situations. First, it is not possible to pass property values into a runScript expression if the property has a datatype that cannot be represented by a string expression. Second, if any varying property is passed into the expression (as a string), it causes a recompile of the python expression, dramatically impacting performance. Third, there is no access within a runScript expression or a function it calls to the bound component or any other information about the binding. Fourth, there is no mechanism to carry state information from one execution to the next.

This objectScript expression function addresses the first deficiency by constructing a typical python "args" tuple from any additional arguments to the function. Such values are available inside the expression either by expanding the tuple in a function call, or by subscripting it for specific items. In its simplest application, this expression function:

objectScript("shared.myMod.myFunction(*args)",
  {Root Container.name},
  {Root Container.myCustomProperty},
  {Root Container.myOtherProperty})

... matches either of these shared module functions:

# module myMod
def myFunction(name, custom, other):
    logger = system.util.getLogger("myMod")
    logger.info(name+": "+str(custom)+"\n"+str(other))
# module myMod
def myFunction(name, *args):
    logger = system.util.getLogger("myMod")
    logger.info(name+": "+"\n".join(args))

You may also reference items in the tuple to deliver values to a script function's keyword arguments. This expression delivers a dataset of tag paths computed from a filter text edit field:

objectScript("system.dataset.toDataSet(['path', 'type'], "+
    "[[x.fullPath, x.dataType] "+
    "for x in system.tag.browseTags(tagpath=args[0], recursive=True) "+
    "if not x.isFolder()])",
  {Root Container.filter.text})

Providing arguments to the expression as a local "args" tuple solves the second deficiency of runScript as a side effect: although you can still embed variables as strings in the expression itself, if you pass all of them as items in args, the expression becomes a string constant. A constant string expression will only be compiled once. All of jython's speed optimizations kick in when compiled code is executed the second time and beyond.

Note that these two deficiencies of runScript() are addressed in Ignition v7.8+, as this same args tuple functionality has been added. The function signatures are still different, as v7.8 runScript() has the pollRate parameter before the variable args.

Functions and expressions used with runScript are difficult to make portable, if the usage needs information local to the bound component. Any access to the calling component from within the runScript callee must be accomplished with getWindow() and then variations on getComponent(). Those lookups must be constants in the callee, or passed as strings in the runScript expression. Such strings need to be updated manually if the component is copied & pasted, it's name or it's window name changed, or anything else changed that would impact the target component's lookup. A component's window and target path are not available anywhere in the expression language in string form. And when using multiple window instances, it can be difficult if not impossible to obtain the correct component reference.

This objectScript() function solves this third deficiency by including the expression's InteractionListener object in the expression's local variables as "binding". In client and designer scopes (component bindings), the InteractionListener is actually an ExpressionPropertyAdapter that has binding details available, in much the same way as an event object makes details available to a event script. Of particular use are "binding.target" (like "event.source") and "binding.targetPropertyName" (like event.propertyName in propertyChange events). The callee may also pass the binding on to a background task with invokeAsync(), and from there execute "binding.childInteractionUpdated()" to safely cause the binding to execute again. (Be careful with that one!)

Ignition v7.8+ addresses the local context deficiency by allowing the use of method names of the bound component, which will naturally carry the 'self' reference to the component. The new runScript() does not provide any means to trigger execution asynchronously.

Finally, objectScript() creates an empty python dictionary when that binding is run the first time and places it in the expression's local vars as "state". Modifications to the dictionary within the expression or any called function are carried to the next execution. This makes it possible to create deltas to previous values and dramatically simplifies animation.

Warning: In gateway context, the "state" dictionary is capable of saving a user defined python class instance through script module restarts. This can retain old behavior if you edit that class definition. If you do this, implement a mechanism that replaces such objects. If you only put python-defined data types in the dictionary, it's safe.

To make full use of the context of an objectScript() function call, use expression syntax similar to this:

objectScript("shared.myMod.myGenericFunction(binding, state, *args)")

One last note: the binding object is null/None in gateway expression tags, as the SQLtags execution engine doesn't assign an InteractionListener to tags. Gateway expression tags execute when the scan class calls for it. Client expression tags do have a binding object, of type ExpressionTagBinding, but it does not have any target properties. It does have the childInteractionUpdated() method, though, if that is of use.