-
Notifications
You must be signed in to change notification settings - Fork 210
klayout Python Module Bridging Code
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:
import klayout.db
some_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 production
a = [ (pt.x,pt.y) for pt in some_polygon.each_point() ]
print repr(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:
import klayout.tl
import klayout.db
import bridge_sample as bs
some_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)) {
return NULL;
}
// Iterate over the array elements
pya::PythonRef iterator (PyObject_GetIter (a));
if (! iterator) {
return NULL;
}
... 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)) {
return NULL;
}
... 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);
}
#else
static
struct PyModuleDef 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) {
return NULL;
}
BridgeError = PyErr_NewException ((char *) "bridge_sample.error", NULL, NULL);
Py_INCREF (BridgeError);
PyModule_AddObject (m, "error", BridgeError);
return m;
}
#endif
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
.
The following code is the translation code to turn polygons into arrays:
// Report an error if the input isn't a db::DSimplePolygon
if (! pya::test_type<const db::DSimplePolygon &> (p)) {
PyErr_SetString (BridgeError, "Expected a db::DSimplePolygon type");
return NULL;
}
// Obtain the db::DSimplePolygon
const 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 tuples
int 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.
// Iterate over the array elements
pya::PythonRef iterator (PyObject_GetIter (a));
if (! iterator) {
return NULL;
}
// 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) {
return NULL;
}
double c[2] = { 0.0, 0.0 };
// Gets the x and y value
for (int i = 0; i < 2; ++i) {
pya::PythonRef xy_item (PyIter_Next (xy_iterator.get ()));
if (! xy_item) {
return NULL;
}
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 errors
if (PyErr_Occurred()) {
return NULL;
}
// 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.