Ignition Extensions - Convenience utilities for advanced users

Introducing a hopefully community-engaged effort to build a useful repository of scripting and other Ignition utilities for everyone to benefit from.

Download the latest release from the Releases page and take advantage of the following functions:


Scripting Functions

system.util

system.util.getContext(): Context

A convenience function to retrieve the local "context" object, useful as an entrypoint to other advanced scripting.

system.util.deepCopy(object): Object

Converts a given Python object of any type into native Python data structures - lists, dictionaries, or "primitive" values (numbers, booleans, strings).

system.util.evalExpression(expression, **arguments): QualifiedValue

Evaluates the supplied string expression as an expression. Additional keyword arguments to the function are provided as reference values inside the expression, e.g.
system.util.evalExpression("{one} + {two}", one=1, two=2)
Any other reference inside curly braces is assumed to be a tag path.
Returns a QualifiedValue.


system.dataset

system.dataset.print(dataset, output=sys.stdout, includeTypes=False)

Pretty-prints an Ignition dataset to the supplied buffer (or, with no argument, the local JVM standard output buffer). If includeTypes is True, adds the simple name of the column's type to the header.

system.dataset.map(dataset, mapper, preserveColumnTypes=False): Dataset

Runs mapper on every row of dataset. mapper will be invoked with keyword arguments, per column in dataset. Return the new values for each row as a list or other sequence. This function allows you to perform multiple updates to a dataset, per-row, as a single operation.

system.dataset.filter(dataset, filter): Dataset

Runs filter on every row of dataset. filter will be invoked with keyword arguments, per column in dataset. The first argument will be the row index, as an int. Return True to keep the column in the output, or False to omit it.

system.dataset.fromExcel(input, headerRow=-1, sheetNumber=0, firstRow=-1, lastRow=-1, firstColumn=-1, lastColumn=-1): Dataset

Attempts to read input as an Excel file (either from a filepath supplied as a string, or as a byte-array already read into memory). Looks on sheetNumber for a contiguous dataset. Provide firstRow, lastRow, firstColumn, and lastColumn to restrict the region of cells read (if not provided, the first/last cells with data will be used). Pass a value for headerRow to extract column names from a particular row (outside of your data region).

system.dataset.equals(dataset, dataset): Boolean

Returns True if both input datasets have the same columns, with the same types, and the same values.

system.dataset.valuesEqual(dataset, dataset): Boolean

Returns True if both input datasets have the same number of rows/columns, and those rows/columns have the same values in the same places.

system.dataset.columnsEqual(dataset, dataset, ignoreCase=False, includeTypes=True): Boolean

Returns True if both input datasets have the same column definitions. Use the optional keyword arguments to make the behavior more lenient.

system.dataset.builder(**columns): DatasetBuilder

Returns a wrapped DatasetBuilder. Provided keyword arguments (if any) are used as column names; the values should be Java or Python types, or the 'short codes' accepted by system.dataset.fromCSV:

alias class
"byt" byte.class
"s" short.class
"i" int.class
"l" long.class
"f" float.class
"d" double.class
"b" bool.class
"Byt" Byte.class
"S" Short.class
"I" Integer.class
"L" Long.class
"F" Float.class
"D" Double.class
"B" Boolean.class
"O" Object.class
"clr" Color.class
"date" Date.class
"cur" Cursor.class
"dim" Dimension.class
"rect" Rectangle.class
"pt" Point.class
"str" String.class
"border" Border.class

In addition, the colTypes function can now be called with the same short codes, or common Python types (e.g. str instead of java.lang.String).


system.project

system.project.getProject(): Project

Returns the internal project object appropriate to your local scope. On the gateway and the client, this is a RuntimeProject; in the designer, this will be a DesignableProject that can be directly mutated.
Also, on the gateway, getProject() can be called with a single string argument to retrieve a project by name.

system.project.save()

In the Designer, essentially programmatically pushes the save button.

system.project.update()

In the Designer, programmatically invokes the 'Update Project' action, which pulls in changes to the local project from the gateway.


Expression Functions

Logic

isAvailable(value)

Returns true if the provided value (i.e. a tag reference) has a quality other than Bad_Disabled or Bad_NotFound.

anyOf(args...), allOf(args...), noneOf(args...)

Logical predicates across the input arguments, interpreted as booleans. Beware vacuous truth on empty lists of arguments.


:warning: Experimental :warning:

system.tag

system.tag.getLocalConfiguration(basePath, recursive=False)

Retrieves only the local configuration for the given tags under path, per the same browsing behavior as system.tag.getConfiguration; i.e., omits any content inherited from parent UDT(s).

40 Likes

Releases are now signed automatically, making things a lot more palatable for actual use.

7 Likes

That sounds awesome, Paul! Super cool initiative! (same with Kindling)

0.3.0 out now, adds system.project extensions.

7 Likes

I already implemented it, so I might just drop it in anyways, but I just pushed a PR for an idea that someone asked for literally years ago: system.util.evalExpression. I'm not sure if anyone would actually find this useful, so I figure I'll ask if anyone has strong opinions.

Usage is pretty simple; provide your expression as the first argument, and any known variables as keyword arguments; your expression can refer to them inside curly braces. Anything in curly braces that isn't a provided variable will be parsed as a tagpath.
E.G:

system.util.evalExpression("123 + {fourFiveSix} + {Realistic0}", fourFiveSix=456)
>>> 
[678.6929315736934, Good, Mon Oct 10 15:52:39 PDT 2022 (1665442359536)]

I figure this could be useful to someone trying to do testing, maybe? I think the original request I saw was something related to tag import/export.

8 Likes

Will that run in a Vision Client? (I vaguely recall discovering the scaffolding was designer & gateway only.)

This is the lower-level evaluation/parsing mechanism used throughout, so should work fine in all scopes; even in a very silly example:

I could see this being useful for building custom in-UI dataset filters.

You think this would play with your simulation aids view function nicely?

1 Like

Probably.

1 Like

Is it possible to add a colour picker component? :slight_smile:

Rejected: Insufficient specification :wink:

Perspective, or Vision? What's the end goal?

1 Like

Haha good answer!

Perspective
Current use case is a css theme manager (Style Classes using CSS properties not in Designer GUI - #14 by rbabcock ) so I can pick a colour and copy the hex or hsla code. Thinking an hsl/rgb picker

I love this idea, however a robust perspective component is significantly more complicated than the other items in this module (because of the weird nuances of perspective). I think the best person to build this, if the time permits, is @ynejati

I have another idea for Perspective components, I can't remember if we talked about it at ICC; a module that does all the gateway level scaffolding, and allow you to just upload an arbitrary JS component bundle that you create however you feel like.

Unrelated idea:

system.util.launch, as a wrapper around ProcessBuilder? Perhaps a context manager, so with system.util.launch() as process: works?
Would need some input on what folks expect to be able to do with a launched process; capture stdout and stderr, certainly, but also to be able to inject arbitrary data to stdin?

7 Likes

That would be neat.

Good idea. Probably not a context manager. Definitely include pipe conveniences. Maybe an analog of python's .communicate() that returned a CompletableFuture. That would enable use of .whenComplete() to run asynchronously. (Satisfies my aversion to waiting in any thread.)

1 Like

Yes we did talk about this at ICC and I think it would be very well received. The scaffolding of perspective is by far the hardest part of adding those components

4 Likes

:smile:

What am I building? A color picker? Sorry, I lost the thread.

5 Likes

Whatever you feel like man! However the specific item someone requested was a color picker.

I just knew that you were the expert on perspective components so I threw you under the bus! Lol

Ha! I'm flattered.

If that's what we are talking about, we have a ticket for this, as a standalone thing. I'm glad you guys brought it up though, since we have some new devs starting this month, and this could be a good one to start them off with.

Also, when the new Form component is implemented (next component in queue), you will be able to select a color picker as an input type when you build out your forms. So, it will be available there as well.

It would be super easy to build one yourself if you wanted to, since its part of the HTML spec, and is implemented by most if not all browsers (I think).

We'll take this component into consideration. Thanks for the suggestion!

9 Likes