Follow

Can I automate Voxler using Python?

Yes, you can! This article contains a sample Python script that is helpful to run if you are scripting in Python; it handles error handling and wraps Voxler's automation model to look like a standard automated application.

To run this script, copy the script below, or click here to download the PY file: Voxler.py.

*********

"""This is a helper module so that users of Voxler automation can 
talk to Voxler with some built in ease of use functions."""

import traceback
import win32com.client
import os

class ApiError(Exception):
    def __init__(self, msg):
        super(ApiError, self).__init__()
        self.msg = msg

    def __str__(self):
        return repr(self.msg)


class Command(object):
    """This encapsulates the error checking and ensures that exceptions are thrown on failure."""

    def __init__(self, api, cmd):
        self.api = api
        self.cmd = cmd

        # Catch all exceptions, get all bad return codes.
        cmd_constructed = False
        try:
            cmd_constructed = self.api.Construct(self.cmd)
        except:
            pass

        if not cmd_constructed:
            raise ApiError("Failed to construct command '{0}'".format(self.cmd))

    def option(self, key, value):
        opt_valid = False
        try:
            opt_valid = self.api.Option(key, value)
        except:
            pass

        if not opt_valid:
            raise ApiError("Failed to set option '{0}':'{1}'".format(key, value))

    def do(self):
        done = False
        try:
            done = self.api.Do()
        except:
            pass

        if not done:
            raise ApiError("Failed to execute command '{0}'".format(self.cmd))

    def doOnce(self):
        done = False
        try:
            done = self.api.DoOnce()
        except:
            pass

        if not done:
            raise ApiError("Failed to execute command '{0}'".format(self.cmd))



class Voxler:
    """Wraps Voxler's automation to look like any other automated application.
        Turns this:
            api.Construct("CreateModule")
            api.Option("Type", "Axes")
            api.Option("AutoConnect", "True")
            api.Option("SourceModule", "ScatterPlot")
            api.Do()

        Into a more consistent, pythonic call:
            voxler.command('CreateModule', {'Type':'Axes', 'AutoConnect':'True', 'SourceModule':'ScatterPlot'})

        And Even:

        voxler.About()
        voxler.Open(Path=voxler.path()+"Samples\\Gold (ScatterPlot).voxb")

        ...
    """

    def __init__(self):
        """Attaches to or creates an instance of Voxler, makes it visible."""

        try:
            self.app = win32com.client.GetObject(Class="Voxler.Application")
        except:
            self.app = win32com.client.Dispatch("Voxler.Application")

        self.app.Visible = 1

        self.api = self.app.CommandApi
        cmd = Command(self.api, "New")
        cmd.do()

    def command(self, cmd_name, opts):
        """ Executes a single command, and sets all of the options.

        Example:

        # Creates an Axes module that connects to the ScatterPlot
        voxler.command('CreateModule', {'Type':'Axes', 'AutoConnect':'True', 'SourceModule':'ScatterPlot'})
        """
        cmd = Command(self.api, cmd_name)

        for k, v in opts.items():
            cmd.option(k, v)

        cmd.do()

    def commandOnce(self, cmd_name, opts):
        """ Executes a single command, and sets all of the options.

        Example:

        # Creates an Axes module that connects to the ScatterPlot
        voxler.command('CreateModule', {'Type':'Axes', 'AutoConnect':'True', 'SourceModule':'ScatterPlot'})
        """
        cmd = Command(self.api, cmd_name)

        for k, v in opts.items():
            cmd.option(k, v)

        cmd.doOnce()

    def command_sequence(self, cmd_name, opts, opts_seq):
        """Executes a command + opts, once for every option in opts_seq.

        Note: the options are not set in order.  The idea for this function is to
        set a whole bunch of options for a module or whatever, and not have to
        keep calling Construct, Option, Do.

        Example:
            Modifies the Axes Module, and for each of the key/values in seqOpts
            executes a new ModifyModule command on the "Axes" module, setting each
            of the options individually.

            voxler.command(
                # This command is created and this option is set
                "ModifyModule", {"Module":"Axes"},

                # For each key/value pair, the above command is executed.
                {
                    "AxesXAxisShow":"True",
                    "AxesXAxisColor":"light green",
                    "AxesXAxisBeg":"20",
                    "AxesXAxisEnd":"110",
                    "AxesXAxisCross1":"0",
                    "AxesXAxisCross2":"50",
                    "AxesXAxisPlane":"45",
                    "AxesXAxisTitle":"X Axis Title",
                    "AxesXAxisShowLabels":"True",
                    "AxesXAxisFlipLabelsX":"True",
                    "AxesXAxisFlipLabelsY":"True"
                }
            )
        """

        for cmdOptKey, cmdOptVal in opts_seq.items():

            # Keep things clean and make the command each time.
            cmd = Command(self.api, cmd_name)

            # Set the initial options common to all commands in this sequence.
            for k, v in opts.items():
                cmd.option(k, v)

            # Set the option for this key/value pair.
            cmd.option(cmdOptKey, cmdOptVal)

            cmd.do()


    def quit(self):
        """Quits Voxler, obviously."""
        self.app.Quit()

    # Special property-like methods.
    def path(self):
        """Returns the path that Voxler is running from.  Useful for loading files."""
        return self.app.Path

    def samplePath(self):
        """Returns the path that Voxler can load samples from."""
        path = os.environ.get('GSSRC')
        if path is not None:
            path = path + "\\Voxler\\Setup\\ProgFiles\\Samples"
        else:
            path = self.path() + "\\Samples"

        return path

    def name(self):
        return self.app.Name

    def fullname(self):
        return self.app.FullName

    def version(self):
        return self.app.Version

    def __getattr__(self, attrib, **kwargs):
        """This function is called when nothing else matches a member for a Voxler object.

        It tries to take the passed in attribute and make a command out of it.

        Examples:
        1. voxler.About()

            Actually runs the behind the scenes:
            api.Construct("About")
            api.Do()

        2. voxler.Open(Path=voxler.path()+"Samples\\Gold (ScatterPlot).voxb")

            Creates an Open command, sets it the Path option, then calls Do on it.
            api.Construct("Open")
            api.Option("Path", ""Samples\\Gold (ScatterPlot).voxb")
            api.Do()
        """

        # Don't intercept the default Python class attributes.
        if attrib.startswith("__") and attrib.endswith("__"):
            return self.__dict__[attrib]

        cmd = Command(self.api, attrib)
        # This returns a function that accepts keyword arguments and then 
        # runs the command in the closure.
        def wrapper(**kwargs):
            for k, v in kwargs.items(): 
                cmd.option(k, v)
            cmd.do()

        return wrapper


# If this is run by itself, then run it as a test.
if __name__ == '__main__':
    voxler = None
    try:
        voxler = Voxler()

        # Launches the About, as expected.
        if False:
            voxler.About()

        voxler.Open(Path=voxler.path()+"Samples\\Gold (ScatterPlot).voxb")

        # Fails, like it should.
        if False:
            voxler.NotReallyACommand()

        # Throws from Voxler.  Like it is supposed to.
        if False:
            voxler.command('Not A Command', {'Type':'Axes', 'AutoConnect':'True', 'SourceModule':'ScatterPlot'})

        # Errors like it is supposed to.
        if False:
            voxler.command(
                "ModifyModule", {
                    "Module":"Axes",
                    "NonExistentOption":"Does it matter?"})

    except:
        print "Unexpected error:"
        traceback.print_exc()

    finally:
        if voxler is not None:
            voxler.quit()
            del voxler

 

Updated November 3, 2016

Was this article helpful?
0 out of 0 found this helpful
Have more questions? Submit a request

0 Comments

Please sign in to leave a comment.