Add vertical rectangles partionning to Rectilinear.

* New: Rectilinear::asRectangles(), decompose the Rectilinear into a
    set of non-overlapping rectangles sliced vertically.
      This not a mathematical minimum set. But should be speedier.
      Done using a sweepline processing the vertical edges.
      Specially suited for use in the extractor which basically
    manage only rectangles.
      Simple algorithm described in pp. 9-11 of Extractor notebook.
      Tests done with unitests/python/test_rectilinear.py, the
    Rectilinear here should cover all the sweepline cases.
This commit is contained in:
Jean-Paul Chaput 2023-05-03 17:08:57 +02:00
parent 4c7cf227be
commit 52fd1c1c40
4 changed files with 524 additions and 23 deletions

View File

@ -40,6 +40,284 @@
#include "hurricane/Warning.h"
namespace {
using namespace std;
using Hurricane::DbU;
using Hurricane::Point;
using Hurricane::Box;
using Hurricane::Interval;
using Hurricane::Rectilinear;
class SweepInterval : public Interval {
public:
inline SweepInterval ( DbU::Unit vmin , DbU::Unit vmax, DbU::Unit xmin );
inline SweepInterval ( Interval&, DbU::Unit xmin );
inline SweepInterval& inflate ( DbU::Unit dvMin, DbU::Unit dvMax );
inline SweepInterval& merge ( DbU::Unit v );
inline DbU::Unit getXMin () const;
inline void setXMin ( DbU::Unit );
inline string _getString () const;
private:
DbU::Unit _xMin;
};
inline SweepInterval::SweepInterval ( DbU::Unit vmin , DbU::Unit vmax, DbU::Unit xmin )
: Interval(vmin,vmax)
, _xMin (xmin)
{ }
inline SweepInterval::SweepInterval ( Interval& base, DbU::Unit xmin )
: Interval(base)
, _xMin (xmin)
{ }
inline SweepInterval& SweepInterval::inflate ( DbU::Unit dvMin, DbU::Unit dvMax ) { Interval::inflate(dvMin,dvMax); return *this; }
inline SweepInterval& SweepInterval::merge ( DbU::Unit v ) { Interval::merge(v); return *this; }
inline DbU::Unit SweepInterval::getXMin () const { return _xMin; }
inline void SweepInterval::setXMin ( DbU::Unit xmin ) { _xMin=xmin; }
inline string SweepInterval::_getString () const
{
string s;
s += "@" + DbU::getValueString(_xMin);
s += " [" + DbU::getValueString(getVMin());
s += " " + DbU::getValueString(getVMax()) + "]";
return s;
}
} // Anonymous namespace.
GETSTRING_VALUE_SUPPORT(::SweepInterval);
namespace {
class SweepLine {
public:
SweepLine ( const Rectilinear*, vector<Box>& );
~SweepLine ();
void addVEdge ( DbU::Unit ymin, DbU::Unit ymax, DbU::Unit x );
void loadVEdges ();
void process ( Interval );
void process ( const pair< DbU::Unit, list<Interval> >& );
void toBox ( SweepInterval& );
void asRectangles ();
private:
const Rectilinear* _rectilinear;
vector<Box>& _boxes;
list< pair< DbU::Unit, list<Interval> > > _vedges;
list< SweepInterval > _sweepLine;
DbU::Unit _prevX;
DbU::Unit _currX;
};
SweepLine::SweepLine ( const Rectilinear* r, vector<Box>& boxes )
: _rectilinear(r)
, _boxes (boxes)
, _vedges ()
, _sweepLine ()
, _prevX (0)
, _currX (0)
{
cdebug_log(17,1) << "SweepLine::SweepLine()" << endl;
}
SweepLine::~SweepLine ()
{
cdebug_tabw(17,-1);
}
void SweepLine::addVEdge ( DbU::Unit ymin, DbU::Unit ymax, DbU::Unit x )
{
if (ymin > ymax) std::swap( ymin, ymax );
cdebug_log(17,1) << "SweepLine::addVEdge() @"<< DbU::getValueString(x)
<< " [" << DbU::getValueString(ymin)
<< " " << DbU::getValueString(ymax) << "]" << endl;
bool inserted = false;
for ( auto ix = _vedges.begin() ; ix != _vedges.end() ; ++ix ) {
cdebug_log(17,0) << "| Looking @" << DbU::getValueString(ix->first)
<< " size=" << ix->second.size() << endl;
if (ix->first > x) {
_vedges.insert( ix, make_pair( x, list<Interval>() ));
cdebug_log(17,0) << "+ add new @" << DbU::getValueString(x) << endl;
--ix;
}
if (ix->first == x) {
for ( auto iintv = ix->second.begin() ; iintv != ix->second.end() ; ++iintv ) {
if (iintv->getVMin() >= ymax) {
ix->second.insert( iintv, Interval(ymin,ymax) );
inserted = true;
break;
}
}
if (not inserted) {
ix->second.push_back( Interval(ymin,ymax) );
inserted = true;
}
break;
}
}
if (not inserted) {
cdebug_log(17,0) << "+ add new (back) @" << DbU::getValueString(x) << endl;
_vedges.push_back( make_pair( x, list<Interval>() ));
_vedges.back().second.push_back( Interval(ymin,ymax) );
}
cdebug_tabw(17,-1);
}
void SweepLine::loadVEdges ()
{
const vector<Point>& points = _rectilinear->getPoints();
for ( size_t i=0 ; i<points.size() ; ++i ) {
const Point& source = points[ i ];
const Point& target = points[ (i+1) % points.size() ];
DbU::Unit dx = target.getX() - source.getX();
//DbU::Unit dy = target.getY() - source.getY();
if (dx == 0) {
addVEdge( source.getY(), target.getY(), source.getX() );
}
}
}
void SweepLine::toBox ( SweepInterval& intv )
{
if (intv.getXMin() == _currX) return;
_boxes.push_back( Box( intv.getXMin(), intv.getVMin()
, _currX , intv.getVMax() ));
intv.setXMin( _currX );
}
void SweepLine::process ( Interval v )
{
cdebug_log(17,1) << "SweepLine::process(Interval&) "
<< " [" << DbU::getValueString(v.getVMin())
<< " " << DbU::getValueString(v.getVMax()) << "]" << endl;
bool done = false;
for ( auto iintv = _sweepLine.begin() ; iintv != _sweepLine.end() ; ++iintv ) {
// Extractor p. 9 (a).
if (v.getVMax() < iintv->getVMin()) {
_sweepLine.insert( iintv, SweepInterval(v,_currX) );
done = true;
break;
}
// Extractor p. 9 (f).
if ( (v.getVMin() == iintv->getVMin())
and (v.getVMax() == iintv->getVMax()) ) {
toBox( *iintv );
_sweepLine.erase( iintv );
done = true;
break;
}
// Extractor p. 9 (b).
if (v.getVMax() == iintv->getVMin()) {
toBox( *iintv );
iintv->merge( v.getVMin() );
done = true;
break;
}
// Extractor p. 9 (g).
if (v.getVMax() == iintv->getVMax()) {
toBox( *iintv );
cdebug_log(17,0) << "case (g): carve" << endl;
iintv->inflate( 0, v.getVMin() - iintv->getVMax() );
cdebug_log(17,0) << "| " << (*iintv) << endl;
done = true;
break;
}
// Extractor p. 9 (h).
if (v.getVMin() == iintv->getVMin()) {
toBox( *iintv );
iintv->inflate(iintv->getVMin() - v.getVMax(), 0 );
done = true;
break;
}
// Extractor p. 9 (c).
if ( (v.getVMin() > iintv->getVMin())
and (v.getVMax() < iintv->getVMax()) ) {
toBox( *iintv );
cdebug_log(17,0) << "case (c): carve" << endl;
DbU::Unit wholeVMin = iintv->getVMin();
iintv->inflate( iintv->getVMin() - v.getVMax(), 0 );
cdebug_log(17,0) << "| " << (*iintv) << endl;
_sweepLine.insert( iintv, SweepInterval( wholeVMin, v.getVMin(), _currX ) );
cdebug_log(17,0) << "| " << (*(--iintv)) << endl;
done = true;
break;
}
// Extractor p. 9 (d,e).
if (v.getVMin() == iintv->getVMax()) {
auto iintvNext = iintv;
++iintvNext;
// Extractor p. 9 (d).
if (iintvNext == _sweepLine.end()) {
toBox( *iintv );
iintv->merge( v.getVMax() );
} else {
// Extractor p. 9 (d).
if (v.getVMax() < iintvNext->getVMin()) {
toBox( *iintv );
iintv->merge( v.getVMax() );
} else {
// Extractor p. 9 (e).
toBox( *iintv );
toBox( *iintvNext );
iintv->merge( iintvNext->getVMax() );
_sweepLine.erase( iintvNext );
}
}
done = true;
break;
}
}
if (not done) {
_sweepLine.push_back( SweepInterval(v,_currX) );
}
cdebug_tabw(17,-1);
}
void SweepLine::process ( const pair< DbU::Unit, list<Interval> >& intervals )
{
cdebug_log(17,1) << "SweepLine::process() @"<< DbU::getValueString(intervals.first)
<< " size=" << intervals.second.size() << endl;
_currX = intervals.first;
for ( const Interval& v : intervals.second ) process( v );
cdebug_tabw(17,-1);
}
void SweepLine::asRectangles ()
{
loadVEdges();
for ( auto intervals : _vedges ) {
process( intervals );
}
cdebug_log(17,0) << "SweepLine::asRectangles() size=" << _boxes.size() << endl;
for ( const Box& b : _boxes )
cdebug_log(17,0) << "| " << b << endl;
}
} // Anonymous namespace.
namespace Hurricane {
@ -50,6 +328,7 @@ namespace Hurricane {
: Super (net)
, _layer (layer)
, _points(points)
, _flags (IsRectilinear)
{ }
@ -61,6 +340,7 @@ namespace Hurricane {
if (points.size() > 1000)
throw Error( "Rectilinear::create(): Rectlinear polygons must not exceed 1000 vertexes." );
bool isRect = true;
DbU::Unit oneGrid = DbU::fromGrid( 1.0 );
for ( size_t i=0 ; i<points.size() ; ++i ) {
size_t j = (i+1) % points.size();
@ -68,9 +348,12 @@ namespace Hurricane {
DbU::Unit dx = std::abs( points[i].getX() - points[j].getX() );
DbU::Unit dy = std::abs( points[i].getY() - points[j].getY() );
if ( (dx != 0) and (dy != 0) and (dx != dy) )
throw Error( "Rectilinear::create(): Can't create, non H/V edge (points %d:%s - %d:%s)."
, i, getString(points[i]).c_str(), j, getString(points[j]).c_str() );
if ((dx != 0) and (dy != 0)) {
isRect = false;
if (dx != dy)
throw Error( "Rectilinear::create(): Can't create, non H/V edge (points %d:%s - %d:%s)."
, i, getString(points[i]).c_str(), j, getString(points[j]).c_str() );
}
if (points[i].getX() % oneGrid)
cerr << Warning( "Rectilinear::create(): In Cell \"%s\", Net \"%s\",\n"
@ -91,7 +374,7 @@ namespace Hurricane {
}
Rectilinear* rectilinear = new Rectilinear ( net, layer, points );
if (not isRect) rectilinear->_flags &= ~IsRectilinear;
rectilinear->_postCreate();
return rectilinear;
@ -176,6 +459,15 @@ namespace Hurricane {
}
bool Rectilinear::getAsRectangles ( std::vector<Box>& rectangles ) const
{
rectangles.clear();
if (not isRectilinear()) return false;
SweepLine( this, rectangles ).asRectangles();
return true;
}
void Rectilinear::_toJson ( JsonWriter* writer ) const
{
Inherit::_toJson( writer );

View File

@ -1,6 +1,6 @@
// -*- C++ -*-
//
// Copyright (c) BULL S.A. 2018-2018, All Rights Reserved
// Copyright (c) BULL S.A. 2018-2023, All Rights Reserved
//
// This file is part of Hurricane.
//
@ -28,10 +28,7 @@
// | C++ Header : "./hurricane/Rectilinear.h" |
// +-----------------------------------------------------------------+
#ifndef HURRICANE_RECTILINEAR_H
#define HURRICANE_RECTILINEAR_H
#pragma once
#include "hurricane/Component.h"
@ -46,11 +43,13 @@ namespace Hurricane {
class Rectilinear : public Component {
public:
typedef Component Super;
static const uint32_t IsRectilinear = (1<<0);
public:
static Rectilinear* create ( Net*, const Layer*, const vector<Point>& );
// Accessors.
virtual bool isNonRectangle () const;
inline bool isRectilinear () const;
virtual DbU::Unit getX () const;
virtual DbU::Unit getY () const;
virtual Box getBoundingBox () const;
@ -59,6 +58,7 @@ namespace Hurricane {
virtual Point getPoint ( size_t i ) const;
virtual const Layer* getLayer () const;
inline Points getContour () const;
bool getAsRectangles ( std::vector<Box>& ) const;
inline const vector<Point>& getPoints () const;
// Mutators.
void setLayer ( const Layer* );
@ -75,11 +75,13 @@ namespace Hurricane {
private:
const Layer* _layer;
vector<Point> _points;
uint32_t _flags;
};
inline Points Rectilinear::getContour () const { return new VectorCollection<Point>(_points); }
inline const vector<Point>& Rectilinear::getPoints () const { return _points; }
inline bool Rectilinear::isRectilinear () const { return _flags & IsRectilinear; }
inline Points Rectilinear::getContour () const { return new VectorCollection<Point>(_points); }
inline const vector<Point>& Rectilinear::getPoints () const { return _points; }
// -------------------------------------------------------------------
@ -99,5 +101,3 @@ namespace Hurricane {
INSPECTOR_P_SUPPORT(Hurricane::Rectilinear);
#endif // HURRICANE_RECTILINEAR_H

View File

@ -45,6 +45,24 @@ namespace Isobar {
}
PyObject* VectorToList ( const std::vector<Box>& v )
{
PyObject* pyList = PyList_New( v.size() );
for ( size_t i=0 ; i<v.size() ; ++i ) {
PyBox* pyBox = PyObject_NEW( PyBox, &PyTypeBox );
if (not pyBox) { return NULL; }
HTRY
pyBox->_object = new Box ( v[i] );
HCATCH
PyList_SetItem( pyList, i, (PyObject*)pyBox );
}
return pyList;
}
extern "C" {
@ -168,20 +186,54 @@ extern "C" {
}
static PyObject* PyRectilinear_getAsRectangles ( PyRectilinear *self, PyObject* args )
{
cdebug_log(20,0) << "Rectilinear.getAsRectangles()" << endl;
HTRY
METHOD_HEAD( "Rectilinear.getAsRectangles()" )
PyObject* pyList = NULL;
if (not PyArg_ParseTuple( args, "O:Rectilinear.getAsRectangles", &pyList )) {
PyErr_SetString( ConstructorError, "Rectilinear.getAsRectangles(): Must have exactly one parameter." );
return NULL;
}
if (not PyList_Check(pyList)) {
PyErr_SetString( ConstructorError, "Rectilinear.getAsRectangles(): Argument must be a list." );
return NULL;
}
PyList_SetSlice( pyList, 0, PyList_Size(pyList), NULL );
vector<Box> boxes;
rectilinear->getAsRectangles( boxes );
for ( size_t i=0 ; i<boxes.size() ; ++i ) {
PyBox* pyBox = PyObject_NEW( PyBox, &PyTypeBox );
if (not pyBox) { return NULL; }
pyBox->_object = new Box ( boxes[i] );
PyList_Append( pyList, (PyObject*)pyBox );
}
HCATCH
Py_RETURN_NONE;
}
// ---------------------------------------------------------------
// PyRectilinear Attribute Method table.
PyMethodDef PyRectilinear_Methods[] =
{ { "create" , (PyCFunction)PyRectilinear_create , METH_VARARGS|METH_STATIC
, "Create a new Rectilinear polygon." }
, { "isNonRectangle", (PyCFunction)PyRectilinear_isNonRectangle, METH_NOARGS , "Tells if the shape is not a rectangle." }
, { "getX" , (PyCFunction)PyRectilinear_getX , METH_NOARGS , "Return the Rectilinear X value." }
, { "getY" , (PyCFunction)PyRectilinear_getY , METH_NOARGS , "Return the Rectilinear Y value." }
, { "getBoundingBox", (PyCFunction)PyRectilinear_getBoundingBox, METH_NOARGS , "Return the Rectilinear Bounding Box." }
, { "setPoints" , (PyCFunction)PyRectilinear_setPoints , METH_VARARGS, "Sets the Rectilinear Bounding Box." }
, { "translate" , (PyCFunction)PyRectilinear_translate , METH_VARARGS, "Translates the Rectilinear of dx and dy." }
, { "destroy" , (PyCFunction)PyRectilinear_destroy , METH_NOARGS
, "Destroy associated hurricane object, the python object remains." }
{ { "create" , (PyCFunction)PyRectilinear_create , METH_VARARGS|METH_STATIC
, "Create a new Rectilinear polygon." }
, { "isNonRectangle" , (PyCFunction)PyRectilinear_isNonRectangle , METH_NOARGS , "Tells if the shape is not a rectangle." }
, { "getX" , (PyCFunction)PyRectilinear_getX , METH_NOARGS , "Return the Rectilinear X value." }
, { "getY" , (PyCFunction)PyRectilinear_getY , METH_NOARGS , "Return the Rectilinear Y value." }
, { "getBoundingBox" , (PyCFunction)PyRectilinear_getBoundingBox , METH_NOARGS , "Return the Rectilinear Bounding Box." }
, { "setPoints" , (PyCFunction)PyRectilinear_setPoints , METH_VARARGS, "Sets the Rectilinear Bounding Box." }
, { "translate" , (PyCFunction)PyRectilinear_translate , METH_VARARGS, "Translates the Rectilinear of dx and dy." }
, { "getAsRectangles", (PyCFunction)PyRectilinear_getAsRectangles, METH_VARARGS, "Return the rectangle coverage." }
, { "destroy" , (PyCFunction)PyRectilinear_destroy , METH_NOARGS
, "Destroy associated hurricane object, the python object remains." }
, {NULL, NULL, 0, NULL} /* sentinel */
};

View File

@ -0,0 +1,157 @@
#!/usr/bin/python
import sys
from coriolis.Hurricane import DataBase, Net, \
DbU, Point, Box, Pad, Rectilinear
from coriolis import Cfg
from coriolis.CRL import AllianceFramework, Catalog, Gds
from coriolis.helpers import l, u
from coriolis.helpers.overlay import CfgCache, UpdateSession
def testRectilinear ( editor ):
"""Check Hurricane.Rectilinear class."""
with CfgCache(priority=Cfg.Parameter.Priority.UserFile) as cfg:
cfg.misc.minTraceLevel = 17000
cfg.misc.maxTraceLevel = 18000
with UpdateSession():
cell = AllianceFramework.get().createCell( 'Rectilinear' )
cell.setTerminalNetlist( True )
cell.setAbutmentBox( Box( l(-5.0), l(-5.0), l(400.0), l(200.0) ) )
#cell.setAbutmentBox( Box( l(-5.0), l(-5.0), l(21.0), l(35.0) ) )
if editor:
editor.setCell( cell )
editor.fit()
technology = DataBase.getDB().getTechnology()
metal1 = technology.getLayer( "METAL1" )
metal2 = technology.getLayer( "METAL2" )
metal3 = technology.getLayer( "METAL3" )
metal4 = technology.getLayer( "METAL4" )
poly = technology.getLayer( "POLY" )
ptrans = technology.getLayer( "PTRANS" )
ntrans = technology.getLayer( "NTRANS" )
pdif = technology.getLayer( "PDIF" )
ndif = technology.getLayer( "NDIF" )
contdifn = technology.getLayer( "CONT_DIF_N" )
contdifp = technology.getLayer( "CONT_DIF_P" )
nwell = technology.getLayer( "NWELL" )
contpoly = technology.getLayer( "CONT_POLY" )
ntie = technology.getLayer( "NTIE" )
with UpdateSession():
net = Net.create( cell, 'my_net' )
net.setExternal( True )
points = [ Point( l( 0.0), l( 0.0) )
, Point( l( 0.0), l( 10.0) )
, Point( l( 20.0), l( 30.0) )
, Point( l( 30.0), l( 30.0) )
, Point( l( 30.0), l( 20.0) )
, Point( l( 10.0), l( 0.0) ) ]
r = Rectilinear.create( net, metal2, points )
#print( 'Normalized and manhattanized contour:' )
#i = 0
#for point in p.getMContour():
# print( '| %d '%i, point, '[%fum %fum]' % ( u(point.getX()), u(point.getY()) ))
# i += 1
#points = [ Point( l( 0.0), l( 40.0) ) # 0
# , Point( l( 30.0), l( 40.0) ) # 1
# , Point( l( 30.0), l( 60.0) ) # 2
# , Point( l( 50.0), l( 60.0) ) # 3
# , Point( l( 50.0), l( 80.0) ) # 4
# , Point( l( 90.0), l( 80.0) ) # 5
# , Point( l( 90.0), l( 50.0) ) # 6
# , Point( l( 60.0), l( 50.0) ) # 7
# , Point( l( 60.0), l( 30.0) ) # 8
# , Point( l( 70.0), l( 30.0) ) # 9
# , Point( l( 70.0), l( 20.0) ) # 10
# , Point( l( 90.0), l( 20.0) ) # 11
# , Point( l( 90.0), l( 0.0) ) # 12
# , Point( l( 20.0), l( 0.0) ) # 13
# , Point( l( 20.0), l( 20.0) ) # 14
# , Point( l( 0.0), l( 20.0) ) ] # 15
points = [ Point( l( 0.0), l( 0.0) ) # 0
, Point( l( 0.0), l( 20.0) ) # 1
, Point( l( 10.0), l( 20.0) ) # 2
, Point( l( 10.0), l( 30.0) ) # 3
, Point( l( 20.0), l( 30.0) ) # 4
, Point( l( 20.0), l( 40.0) ) # 5
, Point( l( 40.0), l( 40.0) ) # 6
, Point( l( 40.0), l( 80.0) ) # 7
, Point( l( 20.0), l( 80.0) ) # 8
, Point( l( 20.0), l( 70.0) ) # 9
, Point( l( 10.0), l( 70.0) ) # 10
, Point( l( 10.0), l( 60.0) ) # 11
, Point( l( 0.0), l( 60.0) ) # 12
, Point( l( 0.0), l(120.0) ) # 13
, Point( l( 10.0), l(120.0) ) # 14
, Point( l( 10.0), l(110.0) ) # 15
, Point( l( 20.0), l(110.0) ) # 16
, Point( l( 20.0), l(100.0) ) # 17
, Point( l( 40.0), l(100.0) ) # 18
, Point( l( 40.0), l(140.0) ) # 19
, Point( l( 20.0), l(140.0) ) # 20
, Point( l( 20.0), l(150.0) ) # 21
, Point( l( 10.0), l(150.0) ) # 22
, Point( l( 10.0), l(160.0) ) # 23
, Point( l( 0.0), l(160.0) ) # 24
, Point( l( 0.0), l(180.0) ) # 25
, Point( l( 40.0), l(180.0) ) # 26
, Point( l( 40.0), l(170.0) ) # 27
, Point( l( 50.0), l(170.0) ) # 28
, Point( l( 50.0), l(160.0) ) # 29
, Point( l(150.0), l(160.0) ) # 30
, Point( l(150.0), l(150.0) ) # 31
, Point( l(130.0), l(150.0) ) # 32
, Point( l(130.0), l(140.0) ) # 33
, Point( l(120.0), l(140.0) ) # 34
, Point( l(120.0), l(130.0) ) # 35
, Point( l(110.0), l(130.0) ) # 36
, Point( l(110.0), l(110.0) ) # 37
, Point( l(120.0), l(110.0) ) # 38
, Point( l(120.0), l(100.0) ) # 39
, Point( l(130.0), l(100.0) ) # 40
, Point( l(130.0), l( 90.0) ) # 41
, Point( l(150.0), l( 90.0) ) # 42
, Point( l(150.0), l( 80.0) ) # 43
, Point( l(120.0), l( 80.0) ) # 44
, Point( l(120.0), l( 70.0) ) # 45
, Point( l(110.0), l( 70.0) ) # 46
, Point( l(110.0), l( 50.0) ) # 47
, Point( l(120.0), l( 50.0) ) # 48
, Point( l(120.0), l( 40.0) ) # 49
, Point( l(130.0), l( 40.0) ) # 50
, Point( l(130.0), l( 30.0) ) # 51
, Point( l(150.0), l( 30.0) ) # 52
, Point( l(150.0), l( 20.0) ) # 53
, Point( l( 50.0), l( 20.0) ) # 54
, Point( l( 50.0), l( 10.0) ) # 55
, Point( l( 40.0), l( 10.0) ) # 56
, Point( l( 40.0), l( 0.0) ) ] # 57
r = Rectilinear.create( net, metal2, points )
boxes = []
r.getAsRectangles( boxes )
#print( 'boxes={}'.format( boxes ))
for box in boxes:
box.translate( l(180.0), l(0.0) )
Pad.create( net, metal3, box )
Gds.save( cell )
def scriptMain ( **kw ):
"""The mandatory function to be called by Coriolis CGT/Unicorn."""
editor = None
if 'editor' in kw and kw['editor']:
editor = kw['editor']
testRectilinear( editor )
return True