Ignition Vision: Print All Windows as Single Multi-Page PDF

Karen Mitchell5 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

Overview

system.print.createPrintJob() wraps a single Swing component into one print job — calling it in a loop spawns one dialog per window. To produce a single multi-page PDF (or print job) covering an entire Ignition Vision project, you must either (a) assemble a java.awt.print.Book of Printable pages from each root container, or (b) rasterize each window into a BufferedImage and stitch the images into a PDF using a library such as Apache PDFBox bundled in your Ignition gateway.

Threading rule: Never call time.sleep() inside a Vision button script. The EDT (Event Dispatch Thread) is blocked, so windows never finish rendering before you capture them. Open all windows in one pass, then use system.util.invokeLater() to defer the capture/print call until the EDT is free to paint.

Prerequisites

Requirement Detail
Ignition version 8.1.x (Vision module); Jython 2.7 scripting engine
Script trigger Button actionPerformed or mouseClicked event on a Vision client
Window permissions All target windows must be accessible to the logged-in user; parameter-required windows need default or dummy parameters to prevent red overlays
PDF output Method A: OS print-to-PDF dialog (no gateway dependency). Method B: Apache PDFBox JAR on gateway classpath (place in /usr/local/ignition/user-lib/pylib/)
Ignition docs system.gui.getWindowNames · system.nav.openWindow · system.print

Method A — Java AWT Book: Single Print Dialog, Multiple Pages

This approach uses java.awt.print.Book (implements Pageable) to collect one Printable per window, then submits the whole book in a single PrinterJob. Selecting "Print to PDF" or "Microsoft Print to PDF" in the OS dialog writes all pages to one file.

# Button actionPerformed — Ignition Vision 8.1
# Jython 2.7
from java.awt.print import PrinterJob, Book, PageFormat, Printable
from java.awt import RenderingHints
import math

# ── 1. Define which windows to capture ──────────────────────────────────────
# Option A: all windows in the project
targetWindows = system.gui.getWindowNames()

# Option B: explicit subset (recommended for production)
# targetWindows = ['Overview', 'Process/Reactor', 'Alarms/ActiveAlarms']

# ── 2. Open every window (no sleep — EDT must stay free) ─────────────────────
openedRoots = []
for name in targetWindows:
    try:
        win = system.nav.openWindow(name)   # opens if not already open
        openedRoots.append(win.getRootContainer())
    except Exception as e:
        system.util.getLogger('PDFExport').warn('Skipping %s: %s' % (name, str(e)))

# ── 3. Build a Printable wrapper for each root container ─────────────────────
class RootPrintable(Printable):
    def __init__(self, component):
        self.component = component

    def print(self, g2d, pf, pageIndex):
        if pageIndex > 0:
            return Printable.NO_SUCH_PAGE
        g2d.translate(pf.getImageableX(), pf.getImageableY())
        iw = pf.getImageableWidth()
        ih = pf.getImageableHeight()
        scaleX = iw / float(self.component.getWidth())
        scaleY = ih / float(self.component.getHeight())
        scale  = min(scaleX, scaleY)
        g2d.scale(scale, scale)
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON)
        self.component.paint(g2d)
        return Printable.PAGE_EXISTS

# ── 4. Defer actual print call until EDT finishes painting all windows ─────────
def submitPrintJob():
    pj = PrinterJob.getPrinterJob()
    book = Book()
    pf = pj.defaultPage()
    pf.setOrientation(PageFormat.LANDSCAPE)

    for rc in openedRoots:
        book.append(RootPrintable(rc), pf)

    pj.setPageable(book)
    pj.setJobName('IgnitionProjectSnapshot')
    if pj.printDialog():        # shows ONE dialog for all pages
        pj.print()

system.util.invokeLater(submitPrintJob)
Note: system.util.invokeLater() queues submitPrintJob to run after all pending paint events clear. This guarantees root containers have rendered before component.paint(g2d) is called. On slow clients add a second invokeLater nesting for extra margin.

Method B — BufferedImage Rasterization to Disk

Use this when you need PNG snapshots for archiving or want to drive a Python PDF library rather than the OS print dialog. Each window is painted into a BufferedImage and saved to a timestamped folder accessible from the gateway file system or a network share.

from java.awt.image import BufferedImage
from java.awt import RenderingHints
from javax.imageio import ImageIO
from java.io import File
import system, os

# Destination directory (gateway-accessible path via system.file or UNC)
OUT_DIR = 'C:/IgnitionSnapshots'
ts = system.date.format(system.date.now(), 'yyyyMMdd_HHmmss')
outPath = os.path.join(OUT_DIR, ts)
os.makedirs(outPath)

targetWindows = ['Overview', 'Process/Boiler', 'Alarms/ActiveAlarms']

def captureWindows():
    for name in targetWindows:
        try:
            win = system.nav.openWindow(name)
            rc  = win.getRootContainer()
            w, h = rc.getWidth(), rc.getHeight()
            img  = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)
            g    = img.createGraphics()
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                               RenderingHints.VALUE_ANTIALIAS_ON)
            rc.paint(g)
            g.dispose()
            safeName = name.replace('/', '_').replace(' ', '_')
            ImageIO.write(img, 'PNG', File('%s/%s.png' % (outPath, safeName)))
        except Exception as e:
            system.util.getLogger('SnapExport').error('%s failed: %s' % (name, str(e)))

system.util.invokeLater(captureWindows)
system.gui.messageBox('Snapshots saved to:\n' + outPath)

Window Selection and Parameter Handling

Windows with required navigation parameters throw exceptions on openWindow(name) if no parameters are passed. Use system.nav.openWindow(name, params) where params is a PyDictionary. Maintain a configuration dictionary at the project script level:

# Project script: shared.pdf.windowConfig
WINDOW_MAP = {
    'Overview':            {},
    'Process/Reactor':     {'unitID': 1, 'mode': 'view'},
    'Process/Boiler':      {'unitID': 2, 'mode': 'view'},
    'Alarms/ActiveAlarms': {},
    'Reports/ShiftSummary': {'shift': 'Day'},
}

# In your button script, replace the openWindow loop:
for name, params in shared.pdf.windowConfig.WINDOW_MAP.items():
    win = system.nav.openWindow(name, params)
    openedRoots.append(win.getRootContainer())
Scenario Recommended Approach Key API
All project windows → single OS-dialog PDF Method A (Book/Pageable) java.awt.print.Book
Subset of windows → PNG archive Method B (BufferedImage) javax.imageio.ImageIO
One window → quick print system.print.createPrintJob(rc) Ignition built-in
Scheduled quarterly report Method B + gateway timer script system.util.invokeLater inside client event

Verification

  1. Add a system.util.getLogger('PDFExport').info('Opened: ' + name) inside the open loop. Check Gateway → Diagnostics → Logs filtered on PDFExport to confirm each window opened without exceptions.
  2. For Method A: after the print dialog appears, verify the page count in the dialog matches len(targetWindows). A mismatch indicates a window failed to open and was skipped.
  3. For Method B: navigate to OUT_DIR and confirm one PNG per window name, file size > 50 KB (near-zero size = paint called before component rendered — add a second invokeLater nesting).
  4. Verify no "red overlay" placeholders appear in captured images — this means a required window parameter was missing. Update WINDOW_MAP with correct defaults.
  5. Test with a non-admin user account to confirm all target windows are within the user's role permissions before scheduling.

FAQ

Why does my captured image show a blank or red window?

The root container painted before the Vision window finished rendering. Wrap your capture logic in a nested system.util.invokeLater() call so the EDT processes all pending paint events first. Red overlays indicate a missing required navigation parameter — pass default values via system.nav.openWindow(name, {'param': value}).

How do I get one PDF file instead of one print dialog per window?

Replace looped system.print.createPrintJob() calls with a java.awt.print.Book containing one Printable per window, then submit it via a single PrinterJob.printDialog() call. The OS "Print to PDF" driver writes all pages to one file.

Can I run this from a gateway timer script instead of a client button?

No. system.nav.openWindow(), getRootContainer(), and AWT painting only work on the Vision client EDT. Schedule the export by firing a client message handler via system.util.sendMessage() from a gateway timer, then execute the print logic inside the client handler.

What Ignition scripting function returns all window names in the project?

system.gui.getWindowNames() returns a list of strings for every window in the open Vision client project. To limit scope, replace this with a hardcoded Python list: targetWindows = ['Overview', 'Alarms/Active'].

How do I set page orientation and margins for each page in the Book?

Create a PageFormat via PrinterJob.defaultPage(), call pf.setOrientation(PageFormat.LANDSCAPE), then set custom margins by creating a java.awt.print.Paper object, calling paper.setImageableArea(x, y, w, h) in points (72 pt = 1 inch), and applying with pf.setPaper(paper) before passing pf to book.append().

Back to blog