Skip to content

klayout Python Module Bridging Code

Matthias Köfferlein edited this page Jun 20, 2018 · 1 revision

Problem

Imagine you have a third-party library that accepts polygons, but not KLayout objects. Instead it takes polygons as arrays of x/y tuples.

The Python solution was to iterate over the KLayout polygon and produce the array:

importklayout.dbsome_polygon=klayout.db.DSimplePolygon(klayout.db.DBox(0, 0, 100, 200))
# make it more complex:some_polygon=some_polygon.round_corners(10.0, 10.0, 200)
# array productiona= [ (pt.x,pt.y) forptinsome_polygon.each_point() ]
printrepr(a)

Not unexpected, the array production loop isn't quite fast. I measured about 3µs per call.

The solution is a specialized C module that performs this translation. I'd like to call such modules "bridges" as they translate representations between different worlds.

With such a bridge, the code looks this way:

importklayout.tlimportklayout.dbimportbridge_sampleasbssome_polygon=klayout.db.DSimplePolygon(klayout.db.DBox(0, 0, 100, 200))
# make it more complex:some_polygon=some_polygon.round_corners(10.0, 10.0, 200)
a=bs.p2a(some_polygon)

Not surprisingly, this code is much faster (about 120ns in my case for the single call to bs.p2a).

The code for the sample bridge is provided in the source tree bridge_sample.cc.

The bridge module skeleton is the one every Python module uses. To simplify the handling of Python object references, the 'pya::PythonRef' smart pointers are used:

#include<Python.h>
#include"pyaRefs.h"
... custom headers ...
static PyObject *BridgeError;
static PyObject *
bridge_a2p (PyObject * /*self*/, PyObject *args)
{
PyObject *a = NULL;
if (! PyArg_ParseTuple (args, "O", &a)) {
returnNULL;
}
// Iterate over the array elements
pya::PythonRef iterator (PyObject_GetIter (a));
if (! iterator) {
returnNULL;
}
... translation code array to polygon ...
}
static PyObject *
bridge_p2a (PyObject * /*self*/, PyObject *args)
{
// Parse the command line arguments
PyObject *p = NULL;
if (! PyArg_ParseTuple (args, "O", &p)) {
returnNULL;
}
... translation code polygon to array ...
}
static PyMethodDef BridgeMethods[] = {
{
"p2a", bridge_p2a, METH_VARARGS,
"Converts a DSimplePolygon to an array."
},
{
"a2p", bridge_a2p, METH_VARARGS,
"Converts an array to a DSimplePolygon."
},
{ NULL, NULL, 0, NULL } // terminal
};
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC
initbridge_sample ()
{
PyObject *m;
m = Py_InitModule ("bridge_sample", BridgeMethods);
if (m == NULL) {
return;
}
BridgeError = PyErr_NewException ((char *) "bridge_sample.error", NULL, NULL);
Py_INCREF (BridgeError);
PyModule_AddObject (m, "error", BridgeError);
}
#elsestaticstructPyModuleDef bridge_module =
{
PyModuleDef_HEAD_INIT,
"bridge_sample",
NULL,
-1,
BridgeMethods
};
PyMODINIT_FUNC
PyInit_bridge_sample ()
{
PyObject *m;
m = PyModule_Create (&bridge_module);
if (m == NULL) {
returnNULL;
}
BridgeError = PyErr_NewException ((char *) "bridge_sample.error", NULL, NULL);
Py_INCREF (BridgeError);
PyModule_AddObject (m, "error", BridgeError);
return m;
}
#endif

Custom headers

The custom headers to use are:

#include"pyaConvert.h"
#include"dbPolygon.h"

The first header supplies the algorithms to access the PyObject's internal object (a db::DSimplePolygon). The second header declares the C++ polygon object. From the various classes this header provides, we use db::DSimplePolygon.

Translation code polygon to array

The following code is the translation code to turn polygons into arrays:

// Report an error if the input isn't a db::DSimplePolygonif (! pya::test_type<const db::DSimplePolygon &> (p)) {
PyErr_SetString (BridgeError, "Expected a db::DSimplePolygon type");
returnNULL;
}
// Obtain the db::DSimplePolygonconst db::DSimplePolygon &poly = pya::python2c<const db::DSimplePolygon &> (p);
// Prepare an array for the points
PyObject *array = PyList_New (poly.hull ().size ());
Py_INCREF (array);
// Iterate over the points and fill the array with x/y tuplesint i = 0;
for (db::DSimplePolygon::polygon_contour_iterator pt = poly.hull ().begin (); pt != poly.hull ().end (); ++pt, ++i) {
PyObject *point = PyTuple_New (2);
PyTuple_SET_ITEM (point, 0, pya::c2python ((*pt).x ()));
PyTuple_SET_ITEM (point, 1, pya::c2python ((*pt).y ()));
PyList_SetItem (array, i, point);
}
return array;

The pya::test_type<T> template provided by pyaConvert.h is used to verify that the requested object is of the desired type.

pya::python2c<T> provided by pyaConvert.h too is used to extract the raw C++ object behind the Python object.

The remaining piece is about iterating over the points in the usual C++ fashion and producing the Python array with the x/y 2-element tuples.

Translation code array to polygon

// Iterate over the array elements
pya::PythonRef iterator (PyObject_GetIter (a));
if (! iterator) {
returnNULL;
}
// Prepare a vector of points we can create the polygon from later
std::vector<db::DPoint> points;
PyObject *item;
while ((item = PyIter_Next (iterator.get ())) != NULL) {
// Iterate over the x/y pair
pya::PythonRef xy_iterator (PyObject_GetIter (item));
if (! xy_iterator) {
returnNULL;
}
double c[2] = { 0.0, 0.0 };
// Gets the x and y valuefor (int i = 0; i < 2; ++i) {
pya::PythonRef xy_item (PyIter_Next (xy_iterator.get ()));
if (! xy_item) {
returnNULL;
}
if (pya::test_type<double> (xy_item.get ())) {
c[i] = pya::python2c<double> (xy_item.get ());
}
}
points.push_back (db::DPoint (c[0], c[1]));
}
// Handle iteration errorsif (PyErr_Occurred()) {
returnNULL;
}
// Create and return a new object of db::DSimplePolygon type
db::DSimplePolygon *poly = new db::DSimplePolygon ();
poly->assign_hull (points.begin (), points.end ());
return pya::c2python_new<db::DSimplePolygon> (poly);

This implementation utilizes the Iterator interface of Python objects to access the nested array/tuple structure and turn this into a std::vector<db::Point> array. The final step is to produce a db::DSimplePolygon object from that list.

The important function is pya::c2python_new<db::DSimplePolygon> from pyaConvert.h. The '_new' version will accept the new object and manage it's lifetime: if the Python object is not longer required, the underlying C++ object will be deleted too. Hence we can safely pass it a new'd pointer.

Clone this wiki locally