Introduction into ruby based automation (RBA)
Using RBA scripts
To use RBA scripts, KLayout must be compiled with the ruby interpreter.
This is done by giving the build script the paths to the ruby headers and library.
For example:
build.sh -rblib /usr/lib/libruby1.8.so -rbinc /usr/lib/ruby/1.8/i486-linux
"rblib" is the path to the ruby shared object, "rbinc" the location of the ruby headers, specifically
"ruby.h". Currently, ruby version 1.8 is required.
To use RBA, the script location must be passed to KLayout using the "-r" option (in this example "hello_world.rb"):
klayout -r hello_world.rb
If used this way, all RBA functionality must be put into one script. Usually, this script will provide all the
classes and definitions required and register new menu items and handlers.
Basic RBA
The ruby script given with the "-r" option is executed before the actual application is
started. In fact, the application execution is initiated by the script, if one is given.
In order to make the application start, the ruby script must contain at least this statement:
RBA::Application.instance.exec
"RBA" is the module provided by KLayout. "Application" is the main controller class (a singleton)
that refers to the application as a whole. It provides the "exec" method which runs the application
and returns if the main window is closed.
In most cases, the script will perform initialization steps before calling "exec" and may do cleanup
once the application returned. Initialization may involve loading of layouts, registering menu items,
initializing other resources etc.
In larger applications
however, source code is usually organised into libraries and a main code part. Libraries and
supplementary code can be loaded prior to the loading of the main source with the "-rm" option. Files loaded
with this option do not need to (and in fact must not) contain the "RBA::Application.instance.exec" call. This allows to
provide independent libraries and initialisation code to a RBA script environment:
klayout -rm setup1.rb -rm setup2.rb -r hello_world.rb
RBA code can be installed globally by creating a file called "rbainit" in the same directory than the
KLayout binary. If such a file is encountered, it will be executed as the first and
before all files specified with "-rm" and "-r" are read.
A simple example
This example script registers a new menu item in the toolbar, which displays a message box saying "Hello, world!"
when selected, and runs the application:
class MenuHandler < RBA::Action
def triggered
RBA::MessageBox::info( "Info", "Hello, world!", RBA::MessageBox::b_ok )
end
end
app = RBA::Application.instance
$menu_handler = MenuHandler.new
$menu_handler.title = "RBA test"
menu = app.main_window.menu
menu.insert_item("@toolbar.end", "rba_test", $menu_handler)
menu.insert_item("tools_menu.end", "rba_test", $menu_handler)
app.exec
This simple example already demonstrates some important concepts:
- Reimplementation: the menu item's functionality is implemented by
reimplementing the Action object's "triggered" method. This method is called
when the menu item is selected.
- Delegation: the menu item is not implemented directly but
the implementation is delegated to an "Action" object. The action provides
the "slot" that the menu item refers to. One action may be used for multiple
menu items. The action does not only provide the implementation but the
title, keyboard shortcut and other properties of the menu item. This way, the
action may be used in multiple places (i.e. menu and toolbar) and still appear
the same.
- Menu item addressing: The menu item is addressed by a "path" expression.
In this case, the path is used for specifying the place where to insert the item.
The path "@toolbar.end" instructs the menu controller to insert the item at the
end of the toolbar. The path "tools_menu.end" instructs it to insert the item at
the end of the "Tools" menu. The second string passed to "insert" is the name
of the new item. After inserting, the new item can be addressed with the path "@toolbar.rba_test"
and "tools_menu.rba_test".
- Ownership of objects: RBA is not able to guarantee a certain lifetime of an
object, because Ruby and C++ implement different lifetime management models. Specifically, for
the action object this means, that the menu controller, which is implemented in C++ cannot
tell ruby that it keeps a reference to the action object. Without further measures, ruby will
ignore this relationsship and delete the action object - the menu item will disappear.
To overcome this problem, an explicit reference to the action object must be held. In this
case, a global variable is used ("$menu_handler"). This could as well be a member of an object
or an array member.
It is very important to keep this aspect in mind when designing RBA applications.
Documentation for the various classes involved can be found in the RBA reference documentation.
Extending the example
To give the menu callback a more "ruby style" look, a wrapper can be created what allows
to attach code to the menu in the style of a ruby iterator. Now the callback uses "yield" to
execute the code attached to the menu. In addition, the menu item now uses a icon and a keyboard
shortcut ("Shift+F7"):
class MenuHandler < RBA::Action
def initialize( t, k, i, &action )
self.title = t
self.shortcut = k
self.icon = i
@action = action
end
def triggered
@action.call( self )
end
private
@action
end
app = RBA::Application.instance
$menu_handler = MenuHandler.new( "RBA test", "Shift+F7", "icon.png" ) {
RBA::MessageBox::info( "Info", "Hello, world!", RBA::MessageBox::b_ok )
}
menu = app.main_window.menu
menu.insert_item("@toolbar.end", "rba_test", $menu_handler)
menu.insert_item("tools_menu.end", "rba_test", $menu_handler)
app.exec
Brief overview over the API
This section describes the main classes that the API provides. The link provides detailed information
about the classes. The documentation uses a special notation to describe the characteristics or
a method and the arguments:
- [static]: A class method is "static" (this is the terminology used in C). Such a method
can be called without an object using the notation "Class.method" or "Class::method". Often these
methods are constructors, i.e. they create objects given a set of parameters.
- [const]: A method is "const", if it does not change the state of an object. This for
example applies to read accessors that just retrieve information but do not alter the object's
state.
- ref (for return values): Some methods return references to objects. This means that Ruby does not
receive a copy of the object but rather a pointer. From the Ruby perspective, this does not
make a difference. From the C++ perspective it means, that the C++ code is the owner of the
object and controls the object's lifetime.
- const ref (for return values): Const references are similar to references. However,
on such references, only "const" methods may be called.
- ref (for arguments): Such arguments receive a reference to the given object.
From the C++ perspective this means, that Ruby is controlling the object's lifetime. Specifically
that means that ruby must maintain an explicit reference to such an object since otherwise the
object gets destroyed by Ruby's garbage collection mechanism which will either withdraw the object
from C++ context or (worse) leave an invalid reference within C++.
The "Action" objects are special in this respect: Technically, "Action" objects are references itself.
Even through Action objects are passed by value, they behave as being passed by reference.
- yield ...: Some methods are iterators. This means that code can be attached to
them, which is called for each object are value delivered by this iterator. This follows the philosophy
of Ruby. However, in some places, "real" iterators are used, i.e. "LayerPropertiesIterator".
This is a brief description of the main classes and the concepts connected with them:
| Class | Description |
| Application |
This is the main application class. There is only one instance representing the
application (a "singleton"). The instance can be retrieved with the "instance" method.
The Application object allows to configure the application on a high level and to
retrieve the MainWindow object, the next basic object.
|
| MainWindow |
This class represents the main window. Since there is only one main window per application
currently, there is only one MainWindow object. This object is managed by the Application
object.
The main window mainly acts as a container for the "layout views", represented by LayoutView
objects. Each view is equivalent to a tab panel in the main window. The main window manages
the views and allows to close views, open new ones and allows to retrieve references to the corresponding
LayoutView objects.
|
| LayoutView |
A LayoutView represents the "canvas" on which one or more layouts are drawn.
The layouts to draw are called "cell views", because basically they show a single cell from a
collection of cells. A cell view is represented by a CellView object
(see CellView documentation).
Multiple cell views can be present in a single LayoutView object.
The "layer views" control, how the cell views are drawn. Basically each layer view is
a receipe how to draw one layer of one cellview and how to show it (colors, fill pattern,
transformations etc.). Layer views can be arranged hierarchically such that groups are formed
with parent nodes controlling the appearance of a group of layer views from a central point.
Layer views are represented by LayerPropertiesNode objects (see LayerPropertiesNode documentation).
|
| Layout |
The Layout object represents the layout database. Layouts are associated with CellView
objects. In principle, multiple CellView objects may refer to the same Layout.
A layout is organized in cells and layers. Each cell contains shapes on the same set of layers and
optionally a set of instances of other cells.
Layout layers must not be confused with the layer views: a layer view is the receipe how to
display a layer from a layout object.
A set of various classes comprise the layout API. The main classes are: Cell,
Shape, CellInstArray, Trans,
Box, Polygon and others.
|
What can be done and what can't
These are examples for what can be done with RBA:
- Customizing the menu, i.e. redefining the keyboard shortcuts or rearranging the menu
- Customizing the layer view list, managing custom stipple pattern
- Automation of tasks like loading of layouts, doing screen shots etc.
- Generating layouts dynamically, i.e. for annotation of other layout or visualization purposes
- Linking KLayout to other applications or databases for example
- Adding custom browsers using the HTML browser dialog (see BrowserDialog documentation)
- Scanning the layout database (i.e. for marker shapes) and performing actions on the results
- Handling properties on shape level (adding and removing)
- Controlling rulers and markers (query, remove and create)
- Combining RBA with qtruby4 (a Ruby wrapper for Qt) to implement custom dialogs etc.
- Generating layout files (there is a "write" function to write a layout to a file).
This are examples for what can't be done with RBA currently:
- Responding to mouse clicks in the canvas (since there is no API for this yet)
More information
The basic source for more information is the RBA reference documentation.
For a deeper understanding of the API, a look at the RBA examples might
be helpful.
For older API versions, a documentation is provided here:
|