Accessing Ignition Project Properties Safely with Python

Daniel Price7 min read
HMI ProgrammingOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

After resolving the active designer or client context, Python can read the project's default datasource name and tag polling rate through getDefaultDatasourceName() and getTagPollRate(). The data path is script → execution-scope detection → UI object hierarchy or active window → context object → property getter. The weak point is context acquisition: this technique uses internal Java objects, was tested on Ignition 7.9.3, and was described as unsupported.

Where does the property request start?

Identify the script's execution scope before calling a context method. An event handler begins with event.source and can walk the component hierarchy. The Designer Script Console and automatically launched scripts have no event object, so they require a separately acquired context reference.

Script location Starting object Context path Primary failure
Designer event handler event.source Ancestor → IgnitionDesigner → getContext() Designer ancestor not found
Client event handler event.source Ancestor → ClientPanel → getClientContext() Client panel not found
Designer Script Console Active Designer frame IgnitionDesigner.getFrame().getContext() No active frame or incompatible internal API
Client script without an event Top-level Swing windows Matching ClientPanel → getClientContext() No uniquely matching client window
Gateway scope Neither of the above Gateway-specific implementation required Client and Designer classes are unavailable

Layer one is object reachability: confirm that the script has either an event source or an active top-level application object. Do not call either property getter until that check identifies a usable path.

How should the script select Designer or Client scope?

system.util.getSystemFlags() supplies execution flags. Test DESIGNER_FLAG as a bit mask rather than comparing the complete flag value for equality. Equality can select the wrong branch when the returned value contains the Designer bit plus another bit.

import system

flags = system.util.getSystemFlags()
inDesigner = bool(flags & system.util.DESIGNER_FLAG)

Import only the class required by the selected branch. A Designer class import does not belong in the client path, and a client runtime class does not establish that the code is in a Designer.

Test Meaning Use
flags & system.util.DESIGNER_FLAG Checks whether the Designer bit is present Preferred branch test
flags == system.util.DESIGNER_FLAG Requires the complete value to equal one flag Avoid when flags may be combined
flags != system.util.DESIGNER_FLAG Treats every non-exact value as a client Does not distinguish a gateway or combined flags

Print or otherwise inspect the Boolean inDesigner in each intended execution location. The check passes only when Designer code selects the Designer branch and a published client selects the client branch.

How does an event handler reach the context?

Walk upward from event.source until the required container type appears. Bound the traversal by checking for None; an unbounded loop otherwise fails indirectly when the parent chain ends.

def getContext(event):
    import system

    comp = event.source
    flags = system.util.getSystemFlags()

    if flags & system.util.DESIGNER_FLAG:
        from com.inductiveautomation.ignition.designer import IgnitionDesigner
        while comp is not None and not isinstance(comp, IgnitionDesigner):
            comp = comp.parent
        if comp is None:
            raise ValueError('IgnitionDesigner ancestor not found')
        return comp.getContext()

    from com.inductiveautomation.factorypmi.application.runtime import ClientPanel
    while comp is not None and not isinstance(comp, ClientPanel):
        comp = comp.parent
    if comp is None:
        raise ValueError('ClientPanel ancestor not found')
    return comp.getClientContext()

This path depends on a component event whose source belongs to the application hierarchy. It is not suitable for the Script Console, startup code without an event, gateway scripts, or a detached Swing object.

Test the returned object before reading project settings: context = getContext(event), followed by print context is not None. A printed True proves the hierarchy and scope branch are connected.

How can code resolve a context without an event?

In the Designer, the direct path is the active Designer frame:

from com.inductiveautomation.ignition.designer import IgnitionDesigner
context = IgnitionDesigner.getFrame().getContext()

In a client, the tested workaround creates a disposable JFrame, obtains the top-level window list, disposes the temporary frame, and searches for a window whose title contains system.util.getProjectName(). It accepts the window only when its content pane is a ClientPanel exposing getClientContext().

import system
from javax.swing import JFrame
from com.inductiveautomation.factorypmi.application.runtime import ClientPanel

projectName = system.util.getProjectName()
frame = JFrame('ThrowAway')
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)
windows = frame.getWindows()
frame.dispose()

context = None
for window in windows:
    try:
        if projectName in window.getTitle():
            pane = window.getContentPane()
            if isinstance(pane, ClientPanel) and hasattr(pane, 'getClientContext'):
                context = pane.getClientContext()
                break
    except AttributeError:
        pass

if context is None:
    raise ValueError('Active client context not found')

The project-name title match is only a candidate filter. The ClientPanel type check is the decisive hop. Breaking after the first valid match prevents a later window from silently replacing the selected context.

Run the resolver in the exact no-event location that needs it. The check passes when context is not None and the temporary ThrowAway frame is no longer displayed.

Which getters return the two project properties?

Once the context exists, the read path is short. Keep acquisition separate from property access so a missing context cannot be mistaken for a missing project value.

defaultDatasourceName = context.getDefaultDatasourceName()
pollRate = context.getTagPollRate()

print defaultDatasourceName, pollRate
Requested setting Context method Verification
Default database or datasource name getDefaultDatasourceName() Compare the returned name with the project's configured default datasource
Polling base rate getTagPollRate() Compare the returned value with the project's configured tag polling rate

Do not assign units to getTagPollRate() from this code alone. Preserve the raw returned value and compare it with the corresponding project setting for the installed release. These calls read values; the shown procedure does not change project configuration.

The check passes only when both printed values match the same open project's settings. A valid context with unexpected values usually points to selection of the wrong project window or execution scope.

How should a cached context be guarded?

A singleton can make the context available to startup scripts, automatic scripts, and the Script Console. Initialize it once, but treat successful resolution—not object construction—as the validity condition.

The tested ActiveContext pattern contains two traps. Its constructor assigns valid = True after resolution even if resolution returned None. Its failure paths call isValid(), which only reads the state; they do not invalidate it. A method named setValid() also toggles state, making repeated calls alternate between valid and invalid.

class ActiveContext(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = super(ActiveContext, cls).__new__(cls)
        return cls.instance

    def __init__(self):
        if not hasattr(self, 'context'):
            self.context = self.resolveContext()
            self.valid = self.context is not None

    def isValid(self):
        return self.valid and self.context is not None

    def getContext(self):
        if not self.isValid():
            raise ValueError('Ignition context is unavailable')
        return self.context

resolveContext() still needs the bit-mask scope selection and the appropriate Designer or client resolver described above. Cache the reference only for the active application lifecycle; clear or reacquire it when that owning context is no longer active.

Call isValid(), then call both getters. The guard passes only when validity is false for a failed resolution and true for a non-null context.

What failures identify the broken hop?

Symptom Likely stopping point Diagnostic action Correction
Designer import or lookup fails Wrong execution branch or incompatible internal API Inspect the Designer flag test and execution location Use the bit-mask test and import inside the Designer branch
ClientPanel ancestor not found Event source is outside the client component hierarchy Trace comp.parent until it becomes None Run from a client component event or use the no-event resolver
Client context remains None No title candidate passed the ClientPanel check Inspect window titles, content-pane types, and getProjectName() Select the actual client window; do not accept title matching alone
Properties belong to another project Ambiguous window selection Compare project name, pane type, and both returned settings Reject ambiguous candidates rather than using the last match
Singleton reports valid with no context Validity initialized independently of resolution Print context is not None beside isValid() Derive validity from the resolved object
Code fails outside 7.9.3 Internal class or object-hierarchy dependency changed Test imports, frame access, pane type, and getters on that installed version Replace the failing internal hop with a supported version-specific integration

This client/Designer technique is not a gateway-context transport. Gateway-level operations require a gateway-scope design; a custom module provides that environment but also requires correct module lifecycle handling. Do not import client Swing classes into gateway code.

For the end-to-end check, run the same read in every required scope, record the selected branch and project name, and compare both returned properties with that project's configuration.

FAQ

Can I read the default Ignition database name from Python?

Yes. Resolve the active Designer or client context, then call context.getDefaultDatasourceName(). Compare the result with the current project's configured default datasource.

Does getTagPollRate return the project polling base rate?

The tested context exposes the polling value through getTagPollRate(). Keep the returned value in its native form and verify it against the project's configured tag polling rate.

Can I access Ignition project properties from the Script Console?

Yes, using the Designer context path IgnitionDesigner.getFrame().getContext(). This internal approach was tested on Ignition 7.9.3, so retest every class and method after a version change.

Does the same context code work in Designer and Client?

No single acquisition path covers both. Test system.util.DESIGNER_FLAG; use getContext() from IgnitionDesigner in Designer scope and getClientContext() from ClientPanel in client scope.

Can I verify the context before using its project properties?

Yes. Confirm the context is non-null, print getDefaultDatasourceName() and getTagPollRate(), and compare both results with the same open project's configuration; that is the final end-to-end verification.

Back to blog