Skip to content

Adding a new machine

Matt Keeter edited this page Nov 18, 2013 · 2 revisions

Overview

There are two steps to creating a new machine

  • Make a UI panel (which should be a class that inherits from OutputPanel)
  • Make a Python module (which should be stored in koko/cam/machines and be registered in koko/cam/machines/__init__.py)

We'll discuss the module first, then go through an example machine UI panel.

Machine module

Each machine is defined by a .py file in koko/cam/machines. Each machine module needs a few module-level parameters:

Parameter Description
INPUT Type of panel that feeds into this machine (Python class)
PANEL This machine's panel (Python class, should inherit from OutputPanel)
DEFAULTS List of tuples, each of which contains a defaults name and a dictionary containing default settings for various types of panels

Let's look at the module-level variables in koko/cam/machines/universal.py as an example:

INPUT = ContourPanel

PANEL = UniversalOutput

DEFAULTS = [
('<None>', {}),
('Cardboard',
    {CadImgPanel:  [('res',5)],
     ContourPanel: [('diameter', 0.25)],
     UniversalOutput: [('power', 25), ('speed', 75),
                       ('rate', 500), ('xmin', 0), ('ymin', 0)]
    }
)
]

This is saying that our machine's UI panel class is UniversalOutput and it should be preceeded by a panel of type ContourPanel. The DEFAULTS array defines two sets of defaults:

  • The first, titled <None>, defines no defaults
  • The second, named Cardboard, defines a default resolution for a CadImgPanel, a default diameter for a ContourPanel, and a set of defaults for the UniversalOutput panel itself.

When a set of defaults is chosen, kokopelli looks through all of the panels that make out the current workflow. If any of those panels appear as keys in the defaults dictionary, it sets the given parameters to the provided values.

Machine UI panel

Each machine needs a UI panel derived from OutputPanel. The OutputPanel class handles many UI aspects automatically. When creating a derived class, you don't actually need to use wx at all (unless you're doing something unusual). Let's look at koko/cam/machines/universal.py as an example.

First, note that we save a class-level variable named extension. This is used by the Save file dialog as the extension of our output files.

Here is the constructor for UniversalOutput, our example class:

    def __init__(self, parent):
        OutputPanel.__init__(self, parent)

        self.construct('Universal laser cutter', [
            ('2D power (%)', 'power', int, lambda f: 0 <= f <= 100),
            ('Speed (%)', 'speed', int, lambda f: 0 <= f <= 100),
            ('Rate','rate', int, lambda f: f > 0),
            ('xmin (mm)', 'xmin', float, lambda f: f >= 0),
            ('ymin (mm)', 'ymin', float, lambda f: f >= 0)
        ], start=True)

We construct the parent class, then call self.construct with a set of arguments. The first argument is the title of this panel; the second is a list of (label, name, type, checker) tuples, and the last is a boolean determining whether there should be a Start button on the UI panel (if false, there's only a Save button).

Next, let's look at the run function:

    def run(self, paths):
        ''' Convert the path from the previous panel into an epilog
            laser file (with .epi suffix).
        '''
        values = self.get_values()
        if not values:  return False

        koko.FRAME.status = 'Converting to .uni file'

        self.file = tempfile.NamedTemporaryFile(suffix=self.extension)
        job_name = koko.APP.filename if koko.APP.filename else 'untitled'

        self.file.write("Z") # initialize
        self.file.write("t%s~;" % job_name) # job name
        self.file.write("IN;DF;PS0;DT~") # initialize
        self.file.write("s%c" % (values['rate']/10)) # ppi

        speed_hi = chr((648*values['speed']) / 256)
        speed_lo = chr((648*values['speed']) % 256)
        self.file.write("v%c%c" % (speed_hi, speed_lo))

        power_hi = chr((320*values['power']) / 256)
        power_lo = chr((320*values['power']) % 256)
        self.file.write("p%c%c" % (power_hi, power_lo))

        self.file.write("a%c" % 2) # air assist on high

        scale = 1000/25.4 # The laser's tick is 1000 DPI
        xoffset = values['xmin']*scale
        yoffset = values['ymin']*scale
        xy = lambda x,y: (xoffset + scale*x, yoffset + scale*y)

        for path in paths:
            self.file.write("PU;PA%d,%d;PD;" % xy(*path.points[0][0:2]))
            for pt in path.points[1:]:
                self.file.write("PA%d,%d;" % xy(*pt[0:2]))
            self.file.write("\n")

        self.file.write("e") # end of file
        self.file.flush()

        koko.FRAME.status = ''

        return True

This function is called when we try to generate the machine-specific path file. The input argument paths is the output from the previous panel in the pipeline (which is a ContourPanel for this machine).

The first function call, self.get_values(), returns a dictionary mapping names (e.g. rate, xmin, ymin defined in the self.construct call) to values.

We create a temporary file named self.file, then write out the machine-specific commands, calling self.file.flush() when finished.

Finally, we return True to indicate success.

Clone this wiki locally