How Do Ignition Maven Modules Expose Jython Functions?

Daniel Price8 min read
HMI ProgrammingOther ManufacturerTutorial / How-to
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

A Vision script call starts in the Vision client JVM. A Perspective script call starts in the gateway JVM. The scripting manager in that JVM resolves the requested namespace and method, then invokes the object registered by the module hook. Only a client function that needs gateway-owned data adds a client-to-gateway RPC hop. Build the module around that execution path; the Maven archetype does not determine runtime scope.

Where does each Ignition script request execute?

Identify the JVM before arranging Maven modules or writing function code. Ignition has three relevant script execution locations: Gateway, Designer, and Vision client. Perspective does not add an independent JVM scope. Its scripts execute in gateway scope with thread-local context that lets Perspective scripting functions operate without additional qualification.

Script caller Execution JVM Module code required Network hop for local logic
Vision client Vision client Client hook and client-accessible implementation None
Designer Designer Designer-accessible hook or implementation, according to the required design-time behavior None
Perspective Gateway Gateway hook and gateway-accessible implementation None
Gateway event or other gateway script Gateway Gateway hook and gateway-accessible implementation None
Vision function accessing gateway-owned resources Vision client, then gateway Client entry point plus gateway RPC implementation Required

A function does not require RPC merely because it is exposed in client scope. Pure calculations, conversions, and transformations can remain inside the client JVM. Add RPC only when the operation must reach a gateway resource or service.

Check: For every planned function, write down its caller and the JVM that owns the required data. Do not proceed until each call has one local execution target or an explicitly identified RPC boundary.

Which Maven configuration assigns the runtime scopes?

The Maven archetype supplies a starting project layout. It does not decide which scopes the completed module supports. The decisive configuration is the set of Maven submodules and the module plugin's projectScopes mapping. A scripting-only module therefore does not need to be forced into either vision-component-archetype or client-designer-gateway-archetype merely to gain a particular scope.

The scripting-function example uses this mapping:

<configuration>
  <projectScopes>
    <projectScope>
      <name>scripting-function-client</name>
      <scope>C</scope>
    </projectScope>
    <projectScope>
      <name>scripting-function-common</name>
      <scope>CDG</scope>
    </projectScope>
    <projectScope>
      <name>scripting-function-designer</name>
      <scope>CD</scope>
    </projectScope>
    <projectScope>
      <name>scripting-function-gateway</name>
      <scope>G</scope>
    </projectScope>
  </projectScopes>
</configuration>
Project Scope code Available locations Typical responsibility
scripting-function-client C Client Vision client hook and client-only services
scripting-function-common CDG Client, Designer, Gateway Shared function logic and objects that have no scope-specific dependency
scripting-function-designer CD Client and Designer Designer-related implementation required by that project layout
scripting-function-gateway G Gateway Gateway hook, Perspective-accessible registration, and gateway services

The exact submodule names may change in another project, but every name in projectScopes must match the corresponding Maven project. The scope code controls where that project is packaged for use.

Check: Inspect the assembled module configuration and confirm that every submodule name maps to the intended scope code. A missing gateway mapping cannot be corrected by registering the function in client code.

What belongs in common, client, designer, and gateway code?

Put logic in the narrowest layer that satisfies its dependencies. Shared code belongs in the common project when it operates only on types available to every target JVM. Registration belongs in the hook for each JVM where the function must appear. Code that uses a client context belongs behind the client hook; code that uses a gateway context belongs behind the gateway hook.

Symptom Likely architectural cause Correction
Function works in Vision but is missing in Perspective Only the client hook registered it Register a gateway-accessible implementation from the gateway hook
Function works in Perspective but is missing in Vision Only the gateway hook registered it Add client registration and package the client project with C scope
Shared implementation cannot compile in all projects Common code imports a scope-specific API Move the dependent portion into the appropriate client or gateway project
Local Vision calculation attempts gateway communication RPC was coupled to the scripting entry point unnecessarily Keep the calculation inside the client JVM and remove the remote dependency
Perspective code was placed in a separate assumed scope Perspective was treated as an independent runtime Place and register the code in gateway scope

A useful split is a common callable object for deterministic transformations and thin scope-specific objects for operations that require a context. This prevents shared code from acquiring client-only or gateway-only dependencies.

Check: Compile each scoped project independently through the normal multi-module build. A common project that requires a client or gateway context has crossed a scope boundary and must be split.

How does the module attach functions to Jython?

Ignition constructs ScriptManager instances in each platform JVM. A module opts into scripting by overriding initializeScriptManager. Ignition calls that hook as script managers are created, giving the module an opportunity to add objects under a scripting namespace.

A gateway hook can register several extension objects:

override fun initializeScriptManager(manager: ScriptManager) {
    manager.apply {
        addScriptModule("system.dataset", DatasetExtensions, ExtensionDocProvider)
        addScriptModule("system.util", UtilitiesExtensions(context), ExtensionDocProvider)
        addScriptModule("system.project", GatewayProjectExtensions(context), ExtensionDocProvider)
        addScriptModule("system.tag", GatewayTagExtensions(context), ExtensionDocProvider)
    }
}

The client hook performs its own registration inside the client JVM:

override fun initializeScriptManager(manager: ScriptManager) {
    manager.apply {
        addScriptModule("system.dataset", DatasetExtensions, ExtensionDocProvider)
        addScriptModule("system.util", UtilitiesExtensions(context), ExtensionDocProvider)
        addScriptModule("system.project", ClientProjectExtensions(context), ExtensionDocProvider)
    }
}

The first argument to addScriptModule is the Jython namespace. The second is the object that supplies callable methods. The third supplies documentation. Sharing a namespace does not make the underlying object global across JVMs: each hook registers the object into its local script manager.

Choose extensions deliberately when adding methods beneath existing system namespaces. A naming collision or incompatible callable surface can make behavior harder to distinguish from built-in functions.

Check: Start the target runtime, open a script environment in that same scope, and resolve the intended namespace. If the namespace or method is absent, check hook execution and local packaging before examining RPC.

How does a Java method accept Jython arguments?

The callable method must bridge Jython values, keyword arguments, Java types, and Python-style exceptions. One implementation exposes deepCopy with a documentation prefix and a required keyword named object:

@ScriptFunction(docBundlePrefix = "UtilitiesExtensions")
@KeywordArgs(names = {"object"}, types = {PyObject.class})
public PyObject deepCopy(PyObject[] args, String[] keywords) {
    PyArgParser parsedArgs = PyArgParser.parseArgs(
        args, keywords, this.getClass(), "deepCopy"
    );
    var toConvert = parsedArgs.getPyObject("object")
        .orElseThrow(() -> Py.TypeError(
            "deepCopy requires one argument, got none"
        ));
    return recursiveConvert(toConvert);
}

@ScriptFunction associates the callable with documentation metadata. @KeywordArgs declares the accepted keyword and expected Jython-facing type. PyArgParser handles positional and keyword input, while Py.TypeError returns a Python-appropriate error when the required value is absent.

Java-to-Jython exposure has subtleties around argument conversion, return values, overloads, documentation, and exception translation. Keep the first implementation narrow: accept a defined argument set, parse it once, return a Jython-compatible object, and produce a direct error for missing input.

Check: Call the method once with a positional value, once with object=, and once with no argument. The first two calls must reach recursiveConvert; the third must report deepCopy requires one argument, got none.

When does a Vision scripting function need RPC?

Follow the request from the caller to the resource. If all work can finish in the Vision client, registration in the client hook is sufficient. If the client entry point needs gateway-owned state or services, the request must cross to gateway code through RPC and return a result or error.

Stage Local client function Client function using gateway RPC
Caller Vision script Vision script
Namespace resolution Client ScriptManager Client ScriptManager
First implementation Client or common object Client proxy or entry point
Transport None Client-to-gateway communication
Resource operation Client JVM Gateway implementation
Result path Direct return to Jython Gateway response returned through the client entry point

Layer one first when diagnosing a remote call. Prove that the client is connected to its gateway and that ordinary gateway communication works. Then prove that the client-side method is registered. Only after those checks should the RPC implementation and gateway handler become the focus.

Perspective does not use this Vision-to-gateway route. Its script already runs on the gateway, so it should invoke gateway-local code unless the target resource is external to Ignition.

Check: Temporarily exercise the client-side logic without the gateway-dependent operation. If local argument parsing works but the resource operation fails, trace the RPC boundary; if the function cannot be resolved, return to scope packaging and hook registration.

How do you commission all required scopes in order?

  1. List the callers. Mark each function for Vision client, Designer, Gateway, Perspective, or a combination. Map Perspective to Gateway.
  2. Mark the data owner. Record whether each operation uses only local arguments, a client context, or a gateway context. This decision establishes whether RPC exists.
  3. Create the scoped Maven projects. Provide common, client, designer, and gateway projects only where their responsibilities require them.
  4. Set projectScopes. Map shared code to CDG, client code to C, the shown designer project to CD, and gateway code to G.
  5. Implement the callable surface. Define the Java or Kotlin object, declare its script metadata, parse positional and keyword arguments, and translate invalid calls into Jython exceptions.
  6. Register each namespace locally. Override initializeScriptManager in every required hook and call addScriptModule with the namespace, implementation object, and documentation provider.
  7. Add RPC only where the path crosses JVMs. Keep local Vision logic local; route only gateway-dependent operations through the client and gateway components.
  8. Test scope isolation. Call the function from Vision, Designer, Gateway, and Perspective only where it is meant to exist. An intentional absence is as useful as a successful call because it proves the packaging boundary.
  9. Run the end-to-end path. Pass a known input from each supported caller, confirm which JVM executes the implementation, compare the returned value with the expected result, and provoke one invalid call to verify exception propagation.

Check: The commissioning record should show the caller, executing JVM, registered namespace, implementation project, whether RPC was used, returned result, and observed error for every supported path.

FAQ

What happens if I use the Vision component archetype for a scripting module?

The archetype does not set the completed module's runtime coverage. Define the required Maven projects and map them explicitly under projectScopes; use C, CD, G, and CDG according to the execution locations.

What happens if I register a function only in the client hook?

Vision clients can receive that registration, but Perspective and gateway scripts will not because they execute in the gateway JVM. Add the corresponding gateway registration when those callers need the function.

What happens if the same function must work in Vision and Perspective?

Place JVM-neutral logic in the common project, register it through both client and gateway hooks, and add RPC only when the Vision path needs gateway-owned resources. Finish by calling it with a known input from Vision and Perspective, confirming the expected result and the executing JVM for both paths.

Back to blog