OsmondCocoa Scripting and CLI

Osmond

OsmondCocoa includes the full-featured scripting language Lua, which can be accessed through a command line interface and also through external programs. From the Lua web site:

What is Lua?

Lua is a powerful, fast, light-weight, embeddable scripting language.

Lua combines simple procedural syntax with powerful data description constructs based on associative arrays and extensible semantics.

Lua is dynamically typed, runs by interpreting bytecode for a register-based virtual machine, and has automatic memory management with incremental garbage collection, making it ideal for configuration, scripting, and rapid prototyping.

Why choose Lua?

Lua is a proven, robust language

Lua has been used in many industrial applications (e.g., Adobe's Photoshop Lightroom), with an emphasis on embedded systems and games (e.g., World of Warcraft). Lua is currently the leading scripting language in games. Lua has a solid reference manual and there are several books about it. Several versions of Lua have been released and used in real applications since its creation in 1993.

In OsmondCocoa, Lua is accessed by pressing the '/' key, which brings up the Command Line Interface (CLI) panel, as shown below.

Osmond

You enter a command by typing into the text entry field at the bottom and pressing the return key. The area at the top is a scrolling log that shows previous commands, printed results, and error messages. To remove the CLI panel, click on the Exit button or press the esc key.

It is customary when describing programming languages to show the canonical "Hello World" example. For Lua, just type:

print("Hello world")

The command and the result appear in the log area as follows:

print("Hello world")
Hello world

For a slightly more compicated example, type the following:

x = 5
y = 2
print("Answer = ", x/y)

The following appear in the log area:

x = 5
y = 2
print("Answer = ", x/y)
Answer = 	2.5

See the Lua website for a full description of the language. In the following sections, we show how Lua interacts with OsmondCocoa.

Grid Spacing and Scale Units

Lua can change the grid spacing using the following command:

grid(x,y)

Where x is the horizontal grid size and y is the vertical grid size, both expressed in the current scale units. When OsmondCocoa is started, the default scale units are mils, so the command grid(100,200) makes the horizontal grid size 100 mils and the vertical grid size 200 mils.

Of course, it is often convenient to use other scale units. To change the default scale units to millimeters, type:

mm_scale()

Now, the command grid(2.5,5) makes the horizontal grid size 2.5 mm and the vertical grid size 5 mm.

To change the default scale units to inches, type:

inch_scale()

And finally, to change the default scale units back to mils, type:

mil_scale()

It may often be more convenient to use scale units other than the default. To accommodate this, OsmondCocoa maintains scaling variables that you can always use to make the right conversions. For example, to make the horizontal grid size 2 mm and the vertical grid size 100 mils, type the following:

grid( 2*mm, 100*mil )

Or type this instead:

grid( 2*mm, 0.1*inch )

This works no matter what the default scale units are. In other words, multiplying by the variables mil, mm, or inch always makes the units come out right.

To change the horizontal grid spacing without changing the vertical grid spacing, use the following command:

xgrid(x)

Where x is in the current scale units. To change the vertical grid spacing without changing the horizontal grid spacing, use the following command:

ygrid(y)

Where y is in the current scale units.

To retrieve the grid origin, you can type the following:

x,y = grid_origin()

Now the two variables x and y hold the x and y coordinates of the grid origin, expressed in absolute current scale units. If you wish to just see the coordinates instead of assigning them to variables, type the following:

print( grid_origin() )

Freehand Drawing

Lua can be used to draw freehand paths on the current layer by using and modifying the state of the Freehand Drawing Tool (also called the Pen). To move the Pen to the absolute coordinates x and y (in current scale units), type the following:

move_pen_to( x, y )

In particular, to move the Pen to the grid origin, type this:

move_pen_to( grid_origin() )

Now, to draw a path from the current Pen location, type the following:

draw_pen_by( dx, dy )

Where dx and dy are the horizontal and vertical distances, relative to the current Pen location, expressed in current scale units. This draws a path from the current Pen location and also modifies the current Pen location. To move the Pen by a horizontal and vertical distance without drawing, type this:

move_pen_by( dx, dy )

To change the width of future freehand paths, type this:

set_pen_width( w )

Where w is the width, in current scale units. To change the spacing of future freehand paths, type this:

set_pen_spacing( s )

Where s is the spacing, in current scale units.

To draw a rectangle, type this:

draw_rect_by( dx, dy )

This draws a rectangle with one corner at the current pen location, and the opposite corner at a location dx distance away in the X direction and dy distance away in the Y direction. For example, to draw a one inch square with the lower left corner at location (1 inch, 1 inch), type the following:

move_pen_to( 1*inch, 1*inch )
draw_rect_by( 1*inch, 1*inch)

To draw an oval, type this:

draw_oval_by( dx, dy )

This draws an oval defined by a rectangle with one corner at the current pen location, and the opposite corner at a location dx distance away in the X direction and dy distance away in the Y direction.

To draw a centered oval, type this:

draw_centered_oval_by( dx, dy )

This draws an oval defined by a rectangle with the center at the current pen location, and a corner at a location dx distance away in the X direction and dy distance away in the Y direction.

Manipulating Parts

As in most languages, Lua variables can hold numerical values and strings. When combined with OsmondCocoa, however, a Lua variable can also hold a reference to a part, like a resistor or a flat pack. And once a variable has a reference to a part, Lua can be used to manipulate that part.

To get a reference to a part, type a command similar to the following:

p = get_part( "R1" )

Where the parameter ("R1") is the string name of the part, either as a literal string or as a variable that contains a string. This means that you could type the following instead:

s = "R1"
p = get_part( s )

Assuming that p is a variable that references a part, then you can move the part by typing:

p:move( dx, dy )

Where dx and dy represent horizontal and vertical offsets, expressed in current scale units.

To rotate the part, type:

p:rotate( angle )

Where angle represents the counter-clockwise rotation, in degrees.

To flip the part from one side of the board to the other, type:

p:flip()

To get position information about the part, you can type the following:

x,y,rot,front = p:position()

Four values are returned. Upon completion the variables x and y contain the horizontal and vertical position coordinates of the part in the current scale units; the rot variable contains the counter-clockwise rotation of the part in degrees; and the front variable contains a Boolean variable that is true if the part is on the front side of the board and false if the part is on the back side of the board.

If you are interested only in the position coordinates, you can type this instead:

x,y = p:position()

The way Lua works, even though the function returns four values, only the first two are assigned to the two variables while the other two values are discarded.

To see all the values printed without assigning them to variables, type this:

print( p:position() )

To get the string name of the part, type this:

part_name = p:name()

To get the value of the part, type:

part_value = p:value()

If the part is defined by a part type, then the following will return the string name of the part type:

part_type = p:type()

To create and position a new part, use a command like this:

part_ref = new_part( type, name, x_pos, y_pos ) 

For example:

p = new_part( "RCR05", "R1", 1000*mil, 2000*mil ) 

Here, the variable p contains a reference to the newly created resistor "R1", which is of type "RCR05", and positioned at absolute coordinates (1000,2000) mils.

Lua Tables

OsmondCocoa uses the Lua Table facility to contain collections of parts. The following command provides a table containing references to all the parts in the part list.

plist = all_parts()

In Lua, tables are indexed starting at 1. If plist is created as shown above, then to print the name of the first part in the part list you can type:

p = plist[1]
print( p:name() )

Or, more succinctly:

print( plist[1]:name() )

To get a table containing references to all selected parts, type this:

selected = selected_parts()

Pins

Given that there is a part in the design called U1, you can get a table of pin references with the following commands:

part = get_part( "U1" )
pins = part:get_pins()

To get a reference to the first pin in the table, do this:

p1 = pins[1]

Now, you can access various bits of information about the pin with command as follows:

print( p1:name() )
print( p1:full_name() )
print( p1:position() )
print( p1:hole_size() )
print( p1:hole_plated() )
print( p1:signal_name() ) 

Something like the following will appear in the log area:

print( p1:name() )
1
print( p1:full_name() )
U1-1
print( p1:position() )
1000	1700
print( p1:hole_size() )
32
print( p1:hole_plated() )
true
print( p1:signal_name() )
**Signal 1**

Pads

A pin has pads on one or more layers of the board. If pn is a reference to a pin, then you can get a reference to the pad on layer 1 with a command like this.

pd1 = pn:pad(1)

You can get references to the pads on the two solder mask layer like this.

pdm1 = pn:mask_pad(1)
pdm2 = pn:mask_pad(2)

Once you have reference to a pad, you can retrieve information about the pad. If pd1 is a reference to a pad, a session might look like this.

print( pd1:width() )
60
print( pd1:height() )
60
print( pd1:shape() )
rectangular
print( pd1:spacing() )
0
print( pd1:rotation() )
0

Signals

A signal can be considered as a collection of pins that should all be connected, and the net list is the collection of all signals in the design. The following command will return a table that contains references to all the signals in the design

my_net_list = net_list()

You can get a reference to the first signal as follows

signal1 = my_net_list[1]

Once you have a reference to a signal, you can print the signal name as follows.

print( signal1:name() )

The following commands then show how to get the list of pins belonging to a signal, and references to the first two pins.

pin_table = signal1:get_pins()
pin1 = pin_table[1]
pin2 = pin_table[2]

Pegs

Pegs keep connectivity information. Every trace segment has a peg at each end and each peg may have several trace segments attached. A pin will have a peg on each layer that has a pad, but pegs can also exist independently of any pin.

If pn is a reference to a pin, then you can get a reference to the peg on layer 1, and print its position with the following commands.

peg1 = pn:peg(1)
print( peg1:position() )

Traces

Traces, or rather, trace segments, represent a line of copper or a line on a silkscreen layer. Every trace has a peg at each end.

If peg1 is a reference to a peg, you can also get a table containing references to all the traces that connect to this peg. The following shows getting the trace table, checking if there any traces, and getting a reference to the first trace if it exists.

trace_table = peg1:traces()
if #trace_table then trace1 = traces[1] end

Once you have a reference to a trace, you can print the width and spacing of the trace like this.

print( trace1:width() )
print( trace1:spacing() )

Also, once you have a reference to a trace, you can get references to the two pegs at either end like this.

peg1,peg2 = trace1:pegs()

PegWebs

A PegWeb is a collection of pegs and traces that are all inteconnected on a single layer. A winding path connecting two pins is a PegWeb. The part outline on a silkscreen layer is a PegWeb. A freehand path made with the Pencil tool is a PegWeb. Mathematically, a PegWeb forms an acyclic, non-directed graph.

The following command returns a table of all the PegWebs that were made as Freehand Paths.

free_pegwebs = get_free_pegwebs()

If pt is a reference to a part, then the following command will return a table of references to all the PegWebs that belong to that part. These will include the PegWebs that make up the part outline on the silkscreen layer, as well as any copper paths that are used in the part definition. In particular, Text parts made with Apple fonts will contain a large number of PegWebs.

peg_web_table = pt:get_pegwebs()

If pw1 is a reference to a PegWeb, then the following will print an indication of what layer the PegWeb is on.

print( pw1:layer() )

Normal layers will be represented by "L1", "L2", etc. Silkscreen layers will be represented by "S1" or "S2". Auxiliary layers will be represented by "A1" or "A2". Solder mask layers will be represented by "M1" or "M2".

If pw1 is a reference to a PegWeb, it is possible to apply a function to all pegs or to all traces of the PegWeb. The following illustrates this with anonymous functions.

pw1:for_all_pegs( function(peg) print( peg:position() ) end )
pw1_for_all_traces( function(trace) print( trace:width() ) end )

Small anonymous functions like those shown have limited use, however. To take full advantage of this feature, you would define functions in an external script and then import the script. The function passed to the for_all_pegs function should take a reference to a peg as the single parameter, and the function passed to the for_all_traces should take a reference to a trace as the single parameter. This is illustrated below:

function show_peg_position( peg )
    print( peg:position() )
end

function show_trace_width( trace )
    print( trace:width() )
end

pw1:for_all_pegs( show_peg_position )
pw1:for_all_traces( show_trace_width )

For finer control over the elements of a PegWeb, you will need to navigate the graph yourself. If pw1 is a reference to a PegWeb, the following will return a reference to the first Peg that belongs to this PegWeb.

peg1 = pw:first_peg()

Once the first peg of a PegWeb is found, the entire PegWeb can be traversed by determining what traces are connected to that peg, and then for each trace, determining what pegs are at both end of that trace, and so on. To prevent backtracking, it is important to be able to tell if two references to pegs are actually referring to the same peg, or that two references to traces are actually referring to the same trace. To facilitate this, references are constructed so that two references are equal (r1 == r2) if and only if they refer to the same thing.

Comment

Not all the available commands are described above. However, the complete command list is shown in the Command Summary below.

In addition to the Command Line Interface, Lua scripts can be created in text files and then run in OsmondCocoa using the Import → Lua Script... command from the File menu. For example, you can use this to define functions that can later be used from the command line. Files containing Lua scripts should be named with a .lua or a .LUA extension.

Although not fully described here, all the facilities of the Lua language, including extensive math and string libraries, are available within OsmondCocoa. Please see the Lua documentation for more detail. In time, more and more of the internal structure of OsmondCocoa will be made accessible to Lua.

Command Summary

Scale and Grid

mil_scale()
Set default scale such that all units are expressed in mils.
inch_scale()
Set default scale such that all units are expressed in inches.
mm_scale()
Set default scale such that all units are expressed in millimeters.
grid(x,y)
Set grid size to x units horizontally by y units vertically.
xgrid(x)
Set horizontal grid size to x units, leaving vertical grid size unchanged.
ygrid(y)
Set vertical grid size to y units, leaving horizontal grid size unchanged.
grid_origin()
Return absolute x and y coordinates of grid origin.
set_grid_origin(x,y)
Set grid origin to absolute coordinates x and y.
set_major_grid( b )
Turn on or off the major grid, on if b is true, off if b is false.
set_minor_grid( b )
Turn on or off the minor grid, on if b is true, off if b is false.

View Commands

set_layer(n)
Make layer n the current layer.
set_silk_layer(n)
Make silkscreen layer n (1 or 2) the current layer.
set_aux_layer(n)
Make auxiliary layer n (1 or 2) the current layer.
set_mask_layer(n)
Make solder mask layer n (1 or 2) the current layer.
get_layer(n)
Returns two items, the category name and the layer number of the current active layer. The category name can be either "signal", "silk", "mask", or "aux".
set_view_zoom(m)
Set the view magnification to m.
set_view_center(x,y)
Scroll the view to place the center at coordinates x and y (if possible).
set_layer_visibility(b)
If b is true, make the current layer visible always.
get_layer_visibility()
Returns true if the current layer is visible always.
set_vector_text(b)
>If b is true, turn on vector text for part names and part values. Otherwise, use Apple fonts for part names and part values.
get_vector_text()
>Returns true if Vector Text for part names and part values is enabled.

Color Commands

In the following color commands, r, g, and b represent red, green, and blue, and are in the range 0.0 to 1.0.

set_background_color(r,g,b)
Set the background color.
get_background_color()
Return the red, green, and blue components of the background color.
set_imperial_grid_color(r,g,b)
Set the color of the grid used for imperial units.
get_imperial_grid_color()
Return the red, green, and blue components of the color of the grid used for imperial units.
set_metric_grid_color(r,g,b)
Set the color of the grid used for metric units.
get_metric_grid_color()
Return the red, green, and blue components of the color of the grid used for metric units.
set_plated_hole_color(r,g,b)
Set the color used for plated holes.
get_plated_hole_color()
Return the red, green, and blue components of the color used for plated holes.
set_unplated_hole_color(r,g,b)
Set the color used for unplated holes.
get_unplated_hole_color()
Return the red, green, and blue components of the color used for unplated holes.
set_selected_signal_trace_color(r,g,b)
Set the color used for the selected signal.
get_selected_signal_trace_color()
Return the red, green, and blue components of the color used for the selected signal.
set_trace_color(r,g,b)
Set the color used for traces in the current layer.
get_trace_color()
Return the red, green, and blue components of the color used for traces in the current layer.
set_pad_color(r,g,b)
Set the color used for pads in the current layer.
get_pad_color()
Return the red, green, and blue components of the color used for pads in the current layer.

Miscellaneous

load_input(s)
Load the string s, where s contains design elements such as Part Types, Parts, Paths, etc., formatted as they normally are in design files.
move(dx,dy)
Move selected items dx units horizontally and dy units vertically.
layer_count()
Return a count of the number of signal layers.
flip_over()
Flip the board over, front to back.
save_as_pdf( path )
Save the current view as a PDF file at the given path.
message_left(s)
Show the message s in the top left message area.
message_right(s)
Show the message s in the top right message area.
refresh()
Refresh the view. This may be be useful in the middle of a long script to allow you to see progress. Otherwise, the view may not be refreshed until the script is complete.
sleep(s)
Pauses the program for s seconds. This may be useful in the middle of a long script, after a refresh, to allow you to inspect the progress.
path_to_file()
Returns a string containing the full path to the design file.
path_to_directory()
Returns a string containing the full path to the directory that contains the design file.
show_dialog(callback,title,label1)
show_dialog(callback,title,label1,label2)
show_dialog(callback,title,label1,label2,label3)
show_dialog(callback,title,label1,label2,label3,label4)
Displays an input dialog with the given title string and from 1 to 4 input label strings. For each label string provided, an input field is also provided. The callback is the string name of the function that gets called when the dialog OK button is pressed. The callback function recieves the values (in order) that the user types into the input fields. Because this command produces a slide-down dialog panel, it only works from within an external script and not from within the existing Command Line Interface panel.

Drawing Commands

move_pen_to(x,y)
Move pen to coordinates x and y.
move_pen_by(dx,dy)
Move pen from current point by dx units horizontally and dy units vertically.
draw_pen_by(dx,dy)
Draw pen from current point by dx units horizontally and dy units vertically.
draw_pen_to(x,y)
Draw pen from current point to coordinates x and y.
draw_rect_by(dx,dy)
Draw rectangle with one corner at the current point and the opposite corner dx units horizontally and dy units vertically away.
draw_oval_by(dx,dy)
Draw oval within an imaginary rectangle such one corner is at the current point and the other corner is dx units horizontally and dy units vertically away.
draw_centered_oval_by(dx,dy)
Draw oval within an imaginary rectangle such that the center is at the current point and a corner is dx units horizontally and dy units vertically away.
set_pen_width(w)
Set pen width to w units.
set_pen_spacing(s)
Set pen spacing to s units.

Parts

The following commands involve acquiring references to parts.

get_part(n)
Return a reference to the part with name "n".
selected_parts()
Return a table of references to the selected parts.
all_parts()
Return a table of references to all parts in the design.
deferred_parts()
Return a table of references to all deferred parts.
new_part(ty,nm,x,y)
Create a new part of type "ty", with name "nm", and place it at x,y. Return a reference to that part.

In the following commands, the variable p is a reference to a part.

p:position()
Return 4 items: the x and y coordinates of the part's position, the rotation of the part in degrees, and a boolean that is true if and only if the part is on the front side.
p:name()
Returns the name of the referenced part.
p:value()
Returns the value of the referenced part.
p:type()
Returns the name of the part type of the referenced part.
p:name_size()
Returns the size of the name text of the referenced part.
p:value_size()
Returns the size of the value text of the referenced part.
p:set_name_size( x )
Set the size of the name text of the referenced part to x.
p:set_value_size( x )
Set the size of the value text of the referenced part to x.
p:move(dx,dy)
Move the referenced part by dx units horizontally and dy units vertically.
p:rotate(r)
Rotate the referenced part by r degrees counter clockwise.
p:flip()
Flip the referenced part from one side of the board to the other.
p:get_pins()
Return a table of references to all the pins of the referenced part.
p:get_pegwebs()
Return a table of references to all PegWebs that belong to this part. This includes all PegWebs used as part outlines on the Silk layers.

Pins

In the following commands, the variable pn is a reference to a pin.

pn:position()
Returns the x and y coordinates of the referenced pin.
pn:name()
Returns the name of the referenced pin.
pn:full_name()
Returns the full name of the referenced pin, including part name.
pn:hole_size()
Returns the hole size of the referenced pin.
pn:hole_plated()
Returns a boolean that indicates if the referenced pin is plated.
pn:set_hole_size(w)
Set hole diameter to w.
pn:set_hole_plated(b)
Set the hole to plated if b is true and to non-plated if b is false.
pn:signal_name()
Returns the signal name of the referenced pin.
pn:pad(n)
Returns a reference to the pad of the referenced pin on layer n.
pn:mask_pad(n)
Returns a reference to the pad of the referenced pin on solder mask layer n (1 or 2).
pn:peg(n)
Returns a reference to the peg of the referenced pin on layer n.

Pads

In the following commands, the variable pd is a reference to a pad.

pd:shape()
Returns the shape of the referenced pad, either "Rectangular" or "Oval".
pd:width()
Returns the width of the referenced pad.
pd:height()
Returns the height of the referenced pad.
pd:spacing()
Returns the spacing adjust of the reference pad.
pd:rotation()
Returns the counter clockwise rotation of the referenced pad in degrees.

Signals and Nets

net_list()
Returns a table of references to all signals.

In the following commands, the variable sg is a reference to a signal.

sg:name()
Returns the name of the referenced signal.
sg:get_pins()
Returns a table of references to all the pins belonging to this signal.

Pegs

In the following commands, the variable pg is a reference to a peg.

pg:position()
Return the x and y coordinates of the peg's position.
pg:traces()
Returns a table of references to traces that are attached to this peg. Up to three traces can be attached to each peg.
pg:pin()
If this pin belongs to a pin, return a reference to that pin, otherwise nil.
get_nearest_peg(x,y)
Returns a reference to the peg nearest coordinates x and y.

Traces

In the following commands, the variable t is a reference to a trace.

t:width()
Return the trace width.
t:length()
Return the trace length.
t:spacing()
Return the trace spacing.
t:set_width(w)
Set width of trace to w.
t:set_spacing(s)
Set spacing of trace to s.
t:pegs()
Return references to the two pegs at each end of the trace.
get_nearest_trace(x,y)
Returns a reference to the trace nearest coordinates x and y.

PegWebs

A PegWeb is a collection of pegs and traces, all tied together on a single layer.

get_free_pegwebs()
Returns a table of references to all the PegWebs made as Freehand Traces.

In the following commands, the variable pw is a reference to a PegWeb.

pw:layer()
Returns a string that indicates the layer of the PegWeb. For normal layers, the string can be "L1", "L2", "L3", etc. For Silkscreen layers, the string can be "S1" or "S2". For Auxiliary layers, the string can be "A1" or "A2". For soldermask layers, the string can be "M1" or "M2".
pw:for_all_pegs( fun )
Applies the function fun to every peg in the PegWeb. The function should take a reference to a peg as the single parameter.
pw:for_all_traces( fun )
Applies the function fun to every trace in the PegWeb. The function should take a reference to a trace as the single parameter.
pw:first_peg()
Returns a reference to the first peg of the PegWeb.