Merge pull request #2050 from KLayout/dev-polygon-graph

Dev polygon graph
This commit is contained in:
Matthias Köfferlein 2025-05-29 09:43:06 +02:00 committed by GitHub
commit 3ce50679fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 9988 additions and 2602 deletions

View File

@ -57,6 +57,9 @@ $python $inst/stubgen.py tl >$pyi_srcdir/tlcore.pyi
echo "Generating stubs for db .."
$python $inst/stubgen.py db tl,lay,rdb >$pyi_srcdir/dbcore.pyi
echo "Generating stubs for pex .."
$python $inst/stubgen.py pex tl,db >$pyi_srcdir/pexcore.pyi
echo "Generating stubs for rdb .."
$python $inst/stubgen.py rdb tl,db >$pyi_srcdir/rdbcore.pyi

View File

@ -592,6 +592,25 @@ _db = Library(
)
config.add_extension(_db)
# ------------------------------------------------------------------
# _pex dependency library
_pex_path = os.path.join("src", "pex", "pex")
_pex_sources = set(glob.glob(os.path.join(_pex_path, "*.cc")))
_pex = Library(
config.root + "._pex",
define_macros=config.macros() + [("MAKE_PEX_LIBRARY", 1)],
include_dirs=[_tl_path, _gsi_path, _db_path, _pex_path],
extra_objects=[config.path_of("_tl", _tl_path), config.path_of("_gsi", _gsi_path), config.path_of("_db", _db_path)],
language="c++",
libraries=config.libraries('_pex'),
extra_link_args=config.link_args("_pex"),
extra_compile_args=config.compile_args("_pex"),
sources=list(_pex_sources),
)
config.add_extension(_pex)
# ------------------------------------------------------------------
# _lib dependency library
@ -869,6 +888,28 @@ db = Extension(
sources=list(db_sources),
)
# ------------------------------------------------------------------
# pex extension library
pex_path = os.path.join("src", "pymod", "pex")
pex_sources = set(glob.glob(os.path.join(pex_path, "*.cc")))
pex = Extension(
config.root + ".pexcore",
define_macros=config.macros(),
include_dirs=[_db_path, _tl_path, _gsi_path, _pya_path, _pex_path],
extra_objects=[
config.path_of("_db", _db_path),
config.path_of("_pex", _pex_path),
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_pya", _pya_path),
],
extra_link_args=config.link_args("pexcore"),
extra_compile_args=config.compile_args("pexcore"),
sources=list(pex_sources),
)
# ------------------------------------------------------------------
# lib extension library
@ -1010,8 +1051,8 @@ if __name__ == "__main__":
package_data={config.root: ["src/pymod/distutils_src/klayout/*.pyi"]},
data_files=[(config.root, ["src/pymod/distutils_src/klayout/py.typed"])],
include_package_data=True,
ext_modules=[_tl, _gsi, _pya, _rba, _db, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img]
ext_modules=[_tl, _gsi, _pya, _rba, _db, _pex, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img]
+ db_plugins
+ [tl, db, lib, rdb, lay, pya],
+ [tl, db, pex, lib, rdb, lay, pya],
cmdclass={'build_ext': klayout_build_ext}
)

View File

@ -32,9 +32,9 @@ HEADERS = \
RESOURCES = \
INCLUDEPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$LYM_INC
DEPENDPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$LYM_INC
LIBS += -L$$DESTDIR -lklayout_tl -lklayout_db -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_lym
INCLUDEPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$PEX_INC $$LYM_INC
DEPENDPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$PEX_INC $$LYM_INC
LIBS += -L$$DESTDIR -lklayout_tl -lklayout_db -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_pex -lklayout_lym
INCLUDEPATH += $$RBA_INC
DEPENDPATH += $$RBA_INC

View File

@ -35,6 +35,7 @@
#include "gsiExpression.h"
#include "libForceLink.h"
#include "rdbForceLink.h"
#include "pexForceLink.h"
#include "lymMacro.h"
#include "lymMacroCollection.h"

View File

@ -19,7 +19,7 @@ SOURCES = $$PWD/bd/main.cc
INCLUDEPATH += $$BD_INC $$TL_INC $$GSI_INC
DEPENDPATH += $$BD_INC $$TL_INC $$GSI_INC
LIBS += -L$$DESTDIR -lklayout_bd -lklayout_db -lklayout_tl -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_lym
LIBS += -L$$DESTDIR -lklayout_bd -lklayout_db -lklayout_pex -lklayout_tl -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_lym
INCLUDEPATH += $$RBA_INC
DEPENDPATH += $$RBA_INC

View File

@ -70,6 +70,9 @@ SOURCES = \
dbNetlistSpiceReaderExpressionParser.cc \
dbObject.cc \
dbObjectWithProperties.cc \
dbPLC.cc \
dbPLCConvexDecomposition.cc \
dbPLCTriangulation.cc \
dbPath.cc \
dbPCellDeclaration.cc \
dbPCellHeader.cc \
@ -105,8 +108,6 @@ SOURCES = \
dbTextWriter.cc \
dbTilingProcessor.cc \
dbTrans.cc \
dbTriangle.cc \
dbTriangles.cc \
dbUserObject.cc \
dbUtils.cc \
dbVector.cc \
@ -308,6 +309,9 @@ HEADERS = \
dbObject.h \
dbObjectTag.h \
dbObjectWithProperties.h \
dbPLC.h \
dbPLCConvexDecomposition.h \
dbPLCTriangulation.h \
dbPath.h \
dbPCellDeclaration.h \
dbPCellHeader.h \
@ -343,8 +347,6 @@ HEADERS = \
dbTextWriter.h \
dbTilingProcessor.h \
dbTrans.h \
dbTriangle.h \
dbTriangles.h \
dbTypes.h \
dbUserObject.h \
dbUtils.h \

View File

@ -83,7 +83,7 @@ MutableRegion::insert (const db::SimplePolygon &polygon)
{
if (polygon.vertices () > 0) {
db::Polygon poly;
poly.assign_hull (polygon.begin_hull (), polygon.end_hull ());
poly.assign_hull (polygon.hull ());
do_insert (poly, 0);
}
}
@ -93,7 +93,7 @@ MutableRegion::insert (const db::SimplePolygonWithProperties &polygon)
{
if (polygon.vertices () > 0) {
db::Polygon poly;
poly.assign_hull (polygon.begin_hull (), polygon.end_hull ());
poly.assign_hull (polygon.hull ());
do_insert (poly, polygon.properties_id ());
}
}

967
src/db/db/dbPLC.cc Normal file
View File

@ -0,0 +1,967 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbPLC.h"
#include "dbLayout.h"
#include "dbWriter.h"
#include "tlStream.h"
#include "tlLog.h"
#include "tlTimer.h"
#include <set>
#include <memory>
#include <vector>
#include <map>
namespace db
{
namespace plc
{
// -------------------------------------------------------------------------------------
// Vertex implementation
Vertex::Vertex (Graph *graph)
: DPoint (), mp_graph (graph), mp_ids (0)
{
// .. nothing yet ..
}
Vertex::Vertex (Graph *graph, const db::DPoint &p)
: DPoint (p), mp_graph (graph), mp_ids (0)
{
// .. nothing yet ..
}
Vertex::Vertex (Graph *graph, const Vertex &v)
: DPoint (), mp_graph (graph), mp_ids (0)
{
operator= (v);
}
Vertex::Vertex (Graph *graph, db::DCoord x, db::DCoord y)
: DPoint (x, y), mp_graph (graph), mp_ids (0)
{
// .. nothing yet ..
}
Vertex::Vertex (const Vertex &v)
: DPoint (), mp_graph (v.mp_graph), mp_ids (0)
{
operator= (v);
}
Vertex::~Vertex ()
{
if (mp_ids) {
delete mp_ids;
mp_ids = 0;
}
}
Vertex &Vertex::operator= (const Vertex &v)
{
if (this != &v) {
// NOTE: edges are not copied!
db::DPoint::operator= (v);
if (mp_ids) {
delete mp_ids;
mp_ids = 0;
}
if (v.mp_ids) {
mp_ids = new std::set<unsigned int> (*v.mp_ids);
}
}
return *this;
}
bool
Vertex::is_outside () const
{
for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) {
if ((*e)->is_outside ()) {
return true;
}
}
return false;
}
void
Vertex::set_is_precious (bool f, unsigned int id)
{
if (f) {
if (! mp_ids) {
mp_ids = new std::set<unsigned int> ();
}
mp_ids->insert (id);
} else {
if (mp_ids) {
delete mp_ids;
mp_ids = 0;
}
}
}
bool
Vertex::is_precious () const
{
return mp_ids != 0;
}
const std::set<unsigned int> &
Vertex::ids () const
{
if (mp_ids != 0) {
return *mp_ids;
} else {
static std::set<unsigned int> empty;
return empty;
}
}
std::vector<Polygon *>
Vertex::polygons () const
{
std::set<Polygon *> seen;
std::vector<Polygon *> res;
for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) {
for (auto t = (*e)->begin_polygons (); t != (*e)->end_polygons (); ++t) {
if (seen.insert (t.operator-> ()).second) {
res.push_back (t.operator-> ());
}
}
}
return res;
}
bool
Vertex::has_edge (const Edge *edge) const
{
for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) {
if (*e == edge) {
return true;
}
}
return false;
}
size_t
Vertex::num_edges (int max_count) const
{
if (max_count < 0) {
// NOTE: this can be slow for a std::list, so we have max_count to limit this effort
return mp_edges.size ();
} else {
size_t n = 0;
for (auto i = mp_edges.begin (); i != mp_edges.end () && --max_count >= 0; ++i) {
++n;
}
return n;
}
}
std::string
Vertex::to_string (bool with_id) const
{
std::string res = tl::sprintf ("(%.12g, %.12g)", x (), y());
if (with_id) {
res += tl::sprintf ("[%x]", (size_t)this);
}
return res;
}
int
Vertex::in_circle (const DPoint &point, const DPoint &center, double radius)
{
double dx = point.x () - center.x ();
double dy = point.y () - center.y ();
double d2 = dx * dx + dy * dy;
double r2 = radius * radius;
double delta = fabs (d2 + r2) * db::epsilon;
if (d2 < r2 - delta) {
return 1;
} else if (d2 < r2 + delta) {
return 0;
} else {
return -1;
}
}
// -------------------------------------------------------------------------------------
// Edge implementation
Edge::Edge (Graph *graph)
: mp_graph (graph), mp_v1 (0), mp_v2 (0), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false)
{
// .. nothing yet ..
}
Edge::Edge (Graph *graph, Vertex *v1, Vertex *v2)
: mp_graph (graph), mp_v1 (v1), mp_v2 (v2), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false)
{
// .. nothing yet ..
}
Edge::~Edge ()
{
// .. nothing yet ..
}
void
Edge::set_left (Polygon *t)
{
mp_left = t;
}
void
Edge::set_right (Polygon *t)
{
mp_right = t;
}
void
Edge::link ()
{
mp_v1->mp_edges.push_back (this);
m_ec_v1 = --mp_v1->mp_edges.end ();
mp_v2->mp_edges.push_back (this);
m_ec_v2 = --mp_v2->mp_edges.end ();
}
void
Edge::unlink ()
{
if (mp_v1) {
mp_v1->remove_edge (m_ec_v1);
}
if (mp_v2) {
mp_v2->remove_edge (m_ec_v2);
}
mp_v1 = mp_v2 = 0;
}
Polygon *
Edge::other (const Polygon *t) const
{
if (t == mp_left) {
return mp_right;
}
if (t == mp_right) {
return mp_left;
}
tl_assert (false);
return 0;
}
Vertex *
Edge::other (const Vertex *t) const
{
if (t == mp_v1) {
return mp_v2;
}
if (t == mp_v2) {
return mp_v1;
}
tl_assert (false);
return 0;
}
bool
Edge::has_vertex (const Vertex *v) const
{
return mp_v1 == v || mp_v2 == v;
}
Vertex *
Edge::common_vertex (const Edge *other) const
{
if (has_vertex (other->v1 ())) {
return (other->v1 ());
}
if (has_vertex (other->v2 ())) {
return (other->v2 ());
}
return 0;
}
std::string
Edge::to_string (bool with_id) const
{
std::string res = std::string ("(") + mp_v1->to_string (with_id) + ", " + mp_v2->to_string (with_id) + ")";
if (with_id) {
res += tl::sprintf ("[%x]", (size_t)this);
}
return res;
}
double
Edge::distance (const db::DEdge &e, const db::DPoint &p)
{
double l = db::sprod (p - e.p1 (), e.d ()) / e.d ().sq_length ();
db::DPoint pp;
if (l <= 0.0) {
pp = e.p1 ();
} else if (l >= 1.0) {
pp = e.p2 ();
} else {
pp = e.p1 () + e.d () * l;
}
return (p - pp).length ();
}
bool
Edge::crosses (const db::DEdge &e, const db::DEdge &other)
{
return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) < 0 &&
other.side_of (e.p1 ()) * other.side_of (e.p2 ()) < 0;
}
bool
Edge::crosses_including (const db::DEdge &e, const db::DEdge &other)
{
int sa = e.side_of (other.p1 ());
int sb = e.side_of (other.p2 ());
int s1 = sa * sb;
int s2 = other.side_of (e.p1 ()) * other.side_of (e.p2 ());
// e can end on other and so can other end on e, but both may not be coincident
return s1 <= 0 && s2 <= 0 && ! (sa == 0 && sb == 0);
}
db::DPoint
Edge::intersection_point (const db::DEdge &e, const db::DEdge &other)
{
return e.intersect_point (other).second;
}
bool
Edge::point_on (const db::DEdge &edge, const db::DPoint &point)
{
if (edge.side_of (point) != 0) {
return false;
} else {
return db::sprod_sign (point - edge.p1 (), edge.d ()) * db::sprod_sign(point - edge.p2 (), edge.d ()) < 0;
}
}
bool
Edge::can_flip () const
{
if (! left () || ! right ()) {
return false;
}
const Vertex *v1 = left ()->opposite (this);
const Vertex *v2 = right ()->opposite (this);
return crosses (db::DEdge (*v1, *v2));
}
bool
Edge::can_join_via (const Vertex *vertex) const
{
if (! left () || ! right ()) {
return false;
}
tl_assert (has_vertex (vertex));
const Vertex *v1 = left ()->opposite (this);
const Vertex *v2 = right ()->opposite (this);
return db::DEdge (*v1, *v2).side_of (*vertex) == 0;
}
bool
Edge::is_outside () const
{
return left () == 0 || right () == 0;
}
bool
Edge::is_for_outside_triangles () const
{
return (left () && left ()->is_outside ()) || (right () && right ()->is_outside ());
}
bool
Edge::has_polygon (const Polygon *t) const
{
return t != 0 && (left () == t || right () == t);
}
// -------------------------------------------------------------------------------------
// Polygon implementation
Polygon::Polygon (Graph *graph)
: mp_graph (graph), m_is_outside (false), m_id (0)
{
// .. nothing yet ..
}
void
Polygon::init ()
{
m_id = 0;
m_is_outside = false;
if (mp_e.empty ()) {
return;
}
std::vector<Edge *> e;
e.swap (mp_e);
std::multimap<Vertex *, Edge *> v2e;
for (auto i = e.begin (); i != e.end (); ++i) {
if (i != e.begin ()) {
v2e.insert (std::make_pair ((*i)->v1 (), *i));
v2e.insert (std::make_pair ((*i)->v2 (), *i));
}
}
mp_e.reserve (e.size ());
mp_e.push_back (e.front ());
mp_v.reserve (e.size ());
mp_v.push_back (mp_e.back ()->v1 ());
auto v = mp_e.back ()->v2 ();
// join the edges in the order of the polygon
while (! v2e.empty ()) {
mp_v.push_back (v);
auto i = v2e.find (v);
tl_assert (i != v2e.end () && i->first == v && i->second != mp_e.back ());
mp_e.push_back (i->second);
v = i->second->other (v);
v2e.erase (i);
i = v2e.find (v);
while (i != v2e.end () && i->first == v) {
if (i->second == mp_e.back ()) {
v2e.erase (i);
break;
}
++i;
}
}
// establish clockwise order of the vertexes
double area = 0.0;
const Vertex *vm1 = vertex (-1), *v0;
for (auto i = mp_v.begin (); i != mp_v.end (); ++i) {
v0 = *i;
area += db::vprod (*vm1 - db::DPoint (), *v0 - *vm1);
vm1 = v0;
}
if (area > db::epsilon) {
std::reverse (mp_v.begin (), mp_v.end ());
std::reverse (mp_e.begin (), mp_e.end ());
std::rotate (mp_e.begin (), ++mp_e.begin (), mp_e.end ());
}
// link the polygon to the edges
for (size_t i = 0; i < size (); ++i) {
Vertex *v = mp_v[i];
Edge *e = mp_e[i];
if (e->v1 () == v) {
e->set_right (this);
} else {
e->set_left (this);
}
}
}
Polygon::Polygon (Graph *graph, Edge *e1, Edge *e2, Edge *e3)
: mp_graph (graph), m_is_outside (false), m_id (0)
{
mp_e.resize (3, 0);
mp_v.resize (3, 0);
mp_e[0] = e1;
mp_v[0] = e1->v1 ();
mp_v[1] = e1->v2 ();
if (e2->has_vertex (mp_v[1])) {
mp_e[1] = e2;
mp_e[2] = e3;
} else {
mp_e[1] = e3;
mp_e[2] = e2;
}
mp_v[2] = mp_e[1]->other (mp_v[1]);
// enforce clockwise orientation
int s = db::vprod_sign (*mp_v[2] - *mp_v[0], *mp_v[1] - *mp_v[0]);
if (s < 0) {
std::swap (mp_v[2], mp_v[1]);
} else if (s == 0) {
// Triangle is not orientable
tl_assert (false);
}
// establish link to edges
for (int i = 0; i < 3; ++i) {
Edge *e = mp_e[i];
unsigned int i1 = 0;
for ( ; e->v1 () != mp_v[i1] && i1 < 3; ++i1)
;
unsigned int i2 = 0;
for ( ; e->v2 () != mp_v[i2] && i2 < 3; ++i2)
;
if ((i1 + 1) % 3 == i2) {
e->set_right (this);
} else {
e->set_left (this);
}
}
}
Polygon::~Polygon ()
{
unlink ();
}
void
Polygon::unlink ()
{
for (auto e = mp_e.begin (); e != mp_e.end (); ++e) {
if ((*e)->left () == this) {
(*e)->set_left (0);
}
if ((*e)->right () == this) {
(*e)->set_right (0);
}
}
}
std::string
Polygon::to_string (bool with_id) const
{
std::string res = "(";
for (int i = 0; i < int (size ()); ++i) {
if (i > 0) {
res += ", ";
}
if (vertex (i)) {
res += vertex (i)->to_string (with_id);
} else {
res += "(null)";
}
}
res += ")";
return res;
}
double
Polygon::area () const
{
return fabs (db::vprod (mp_e[0]->d (), mp_e[1]->d ())) * 0.5;
}
db::DBox
Polygon::bbox () const
{
db::DBox box;
for (auto i = mp_v.begin (); i != mp_v.end (); ++i) {
box += **i;
}
return box;
}
db::DPolygon
Polygon::polygon () const
{
std::vector<db::DPoint> pts;
for (int i = 0; i < int (size ()); ++i) {
pts.push_back (*vertex (i));
}
db::DPolygon poly;
poly.assign_hull (pts.begin (), pts.end (), false);
return poly;
}
std::pair<db::DPoint, double>
Polygon::circumcircle (bool *ok) const
{
tl_assert (mp_v.size () == 3);
// see https://en.wikipedia.org/wiki/Circumcircle
// we set A=(0,0), so the formulas simplify
if (ok) {
*ok = true;
}
db::DVector b = *mp_v[1] - *mp_v[0];
db::DVector c = *mp_v[2] - *mp_v[0];
double b2 = b.sq_length ();
double c2 = c.sq_length ();
double sx = 0.5 * (b2 * c.y () - c2 * b.y ());
double sy = 0.5 * (b.x () * c2 - c.x() * b2);
double a1 = b.x() * c.y();
double a2 = c.x() * b.y();
double a = a1 - a2;
double a_abs = std::abs (a);
if (a_abs < (std::abs (a1) + std::abs (a2)) * db::epsilon) {
if (ok) {
*ok = false;
return std::make_pair (db::DPoint (), 0.0);
} else {
tl_assert (false);
}
}
double radius = sqrt (sx * sx + sy * sy) / a_abs;
db::DPoint center = *mp_v[0] + db::DVector (sx / a, sy / a);
return std::make_pair (center, radius);
}
Vertex *
Polygon::opposite (const Edge *edge) const
{
tl_assert (mp_v.size () == 3);
for (int i = 0; i < 3; ++i) {
Vertex *v = mp_v[i];
if (! edge->has_vertex (v)) {
return v;
}
}
tl_assert (false);
}
Edge *
Polygon::opposite (const Vertex *vertex) const
{
tl_assert (mp_v.size () == 3);
for (int i = 0; i < 3; ++i) {
Edge *e = mp_e[i];
if (! e->has_vertex (vertex)) {
return e;
}
}
tl_assert (false);
}
Edge *
Polygon::find_edge_with (const Vertex *v1, const Vertex *v2) const
{
for (auto e = mp_e.begin (); e != mp_e.end (); ++e) {
if ((*e)->has_vertex (v1) && (*e)->has_vertex (v2)) {
return *e;
}
}
tl_assert (false);
}
Edge *
Polygon::common_edge (const Polygon *other) const
{
for (auto e = mp_e.begin (); e != mp_e.end (); ++e) {
if ((*e)->other (this) == other) {
return *e;
}
}
return 0;
}
int
Polygon::contains (const db::DPoint &point) const
{
tl_assert (mp_v.size () == 3);
auto c = *mp_v[2] - *mp_v[0];
auto b = *mp_v[1] - *mp_v[0];
int vps = db::vprod_sign (c, b);
if (vps == 0) {
return db::vprod_sign (point - *mp_v[0], b) == 0 && db::vprod_sign (point - *mp_v[0], c) == 0 ? 0 : -1;
}
int res = 1;
const Vertex *vl = mp_v[2];
for (int i = 0; i < 3; ++i) {
const Vertex *v = mp_v[i];
int n = db::vprod_sign (point - *vl, *v - *vl) * vps;
if (n < 0) {
return -1;
} else if (n == 0) {
res = 0;
}
vl = v;
}
return res;
}
Edge *
Polygon::next_edge (const Edge *edge, const Vertex *vertex) const
{
for (auto e = mp_e.begin (); e != mp_e.end (); ++e) {
if (*e != edge && ((*e)->v1 () == vertex || (*e)->v2 () == vertex)) {
return *e;
}
}
return 0;
}
double
Polygon::min_edge_length () const
{
double lmin = mp_e[0]->d ().length ();
for (auto e = mp_e.begin (); e != mp_e.end (); ++e) {
lmin = std::min (lmin, (*e)->d ().length ());
}
return lmin;
}
double
Polygon::b () const
{
double lmin = min_edge_length ();
bool ok = false;
auto cr = circumcircle (&ok);
return ok ? lmin / cr.second : 0.0;
}
bool
Polygon::has_segment () const
{
for (auto e = mp_e.begin (); e != mp_e.end (); ++e) {
if ((*e)->is_segment ()) {
return true;
}
}
return false;
}
unsigned int
Polygon::num_segments () const
{
unsigned int n = 0;
for (auto e = mp_e.begin (); e != mp_e.end (); ++e) {
if ((*e)->is_segment ()) {
++n;
}
}
return n;
}
// -----------------------------------------------------------------------------------
Graph::Graph ()
: m_id (0)
{
// .. nothing yet ..
}
Graph::~Graph ()
{
clear ();
}
Vertex *
Graph::create_vertex (double x, double y)
{
m_vertex_heap.push_back (Vertex (this, x, y));
return &m_vertex_heap.back ();
}
Vertex *
Graph::create_vertex (const db::DPoint &pt)
{
m_vertex_heap.push_back (Vertex (this, pt));
return &m_vertex_heap.back ();
}
Edge *
Graph::create_edge (Vertex *v1, Vertex *v2)
{
Edge *edge = 0;
if (! m_returned_edges.empty ()) {
edge = m_returned_edges.back ();
m_returned_edges.pop_back ();
*edge = Edge (this, v1, v2);
} else {
m_edges_heap.push_back (Edge (this, v1, v2));
edge = &m_edges_heap.back ();
}
edge->link ();
edge->set_id (++m_id);
return edge;
}
Polygon *
Graph::create_triangle (Edge *e1, Edge *e2, Edge *e3)
{
Polygon *res = new Polygon (this, e1, e2, e3);
res->set_id (++m_id);
mp_polygons.push_back (res);
return res;
}
void
Graph::remove_polygon (Polygon *poly)
{
std::vector<Edge *> edges;
edges.resize (poly->size (), 0);
for (int i = 0; i < int (poly->size ()); ++i) {
edges [i] = poly->edge (i);
}
delete poly;
// clean up edges we do no longer need
for (auto e = edges.begin (); e != edges.end (); ++e) {
if ((*e) && (*e)->left () == 0 && (*e)->right () == 0 && (*e)->v1 ()) {
(*e)->unlink ();
m_returned_edges.push_back (*e);
}
}
}
std::string
Graph::to_string ()
{
std::string res;
for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) {
if (! res.empty ()) {
res += ", ";
}
res += t->to_string ();
}
return res;
}
db::DBox
Graph::bbox () const
{
db::DBox box;
for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) {
box += t->bbox ();
}
return box;
}
db::Layout *
Graph::to_layout (bool decompose_by_id) const
{
db::Layout *layout = new db::Layout ();
layout->dbu (0.001);
auto dbu_trans = db::CplxTrans (layout->dbu ()).inverted ();
db::Cell &top = layout->cell (layout->add_cell ("DUMP"));
unsigned int l1 = layout->insert_layer (db::LayerProperties (1, 0));
unsigned int l2 = layout->insert_layer (db::LayerProperties (2, 0));
unsigned int l10 = layout->insert_layer (db::LayerProperties (10, 0));
unsigned int l20 = layout->insert_layer (db::LayerProperties (20, 0));
unsigned int l21 = layout->insert_layer (db::LayerProperties (21, 0));
unsigned int l22 = layout->insert_layer (db::LayerProperties (22, 0));
std::vector<db::DPoint> pts;
for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) {
pts.clear ();
for (int i = 0; i < int (t->size ()); ++i) {
pts.push_back (*t->vertex (i));
}
db::DPolygon poly;
poly.assign_hull (pts.begin (), pts.end ());
top.shapes (t->is_outside () ? l2 : l1).insert (dbu_trans * poly);
if (decompose_by_id) {
if ((t->id () & 1) != 0) {
top.shapes (l20).insert (dbu_trans * poly);
}
if ((t->id () & 2) != 0) {
top.shapes (l21).insert (dbu_trans * poly);
}
if ((t->id () & 4) != 0) {
top.shapes (l22).insert (dbu_trans * poly);
}
}
}
for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) {
if ((e->left () || e->right ()) && e->is_segment ()) {
top.shapes (l10).insert (dbu_trans * e->edge ());
}
}
return layout;
}
void
Graph::dump (const std::string &path, bool decompose_by_id) const
{
std::unique_ptr<db::Layout> ly (to_layout (decompose_by_id));
tl::OutputStream stream (path);
db::SaveLayoutOptions opt;
db::Writer writer (opt);
writer.write (*ly, stream);
tl::info << "Graph written to " << path;
}
void
Graph::clear ()
{
mp_polygons.clear ();
m_edges_heap.clear ();
m_vertex_heap.clear ();
m_returned_edges.clear ();
m_id = 0;
}
} // namespace plc
} // namespace db

908
src/db/db/dbPLC.h Normal file
View File

@ -0,0 +1,908 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_dbPLC
#define HDR_dbPLC
#include "dbCommon.h"
#include "dbBox.h"
#include "dbRegion.h"
#include "tlObjectCollection.h"
#include "tlStableVector.h"
#include <limits>
#include <list>
#include <vector>
#include <algorithm>
namespace db
{
class Layout;
namespace plc
{
/**
* @brief A framework for piecewise linear curves
*
* This framework implements classes for dealing with piecewise linear
* curves. It is the basis for triangulation and polygon decomposition
* algorithms.
*
* The core class is the PLCGraph which is a collection of vertices,
* edges and edge loops (polygons). Vertices, edges and polygons form
* graphs.
*
* A "vertex" (db::plc::Vertex) is a point. A point connects two or
* more edges.
* A vertex has some attributes:
* * 'precious': if set, the vertex is not removed during triangulation
* for example.
*
* An "edge" (db::plc::Edge) is a line connecting two vertexes. The
* edge runs from vertex v1 to vertex v2. An edge separates two
* polygons (left and right, as seen in the run direction).
*
* A "segment" as an edge that is part of an original polygon outline.
*
* A "polygon" (db::plc::Polygon) is a loop of edges.
*/
class Polygon;
class Edge;
class Graph;
/**
* @brief A class representing a vertex in a Delaunay triangulation graph
*
* The vertex carries information about the connected edges and
* an integer value that can be used in traversal algorithms
* ("level")
*/
class DB_PUBLIC Vertex
: public db::DPoint
{
public:
typedef std::list<Edge *> edges_type;
typedef edges_type::const_iterator edges_iterator;
typedef edges_type::iterator edges_iterator_non_const;
Vertex (const Vertex &v);
Vertex &operator= (const Vertex &v);
/**
* @brief Gets a value indicating whether any of the attached edges is "outside"
*/
bool is_outside () const;
/**
* @brief Gets a list of polygons that are attached to this vertex
*/
std::vector<Polygon *> polygons() const;
/**
* @brief Gets the graph object this vertex belongs to
*/
Graph *graph () const { return mp_graph; }
/**
* @brief Iterates the edges on this vertex (begin)
*/
edges_iterator begin_edges () const { return mp_edges.begin (); }
/**
* @brief Iterates the edges on this vertex (end)
*/
edges_iterator end_edges () const { return mp_edges.end (); }
/**
* @brief Returns the number of edges attached to this vertex
*/
size_t num_edges (int max_count = -1) const;
/**
* @brief Returns a value indicating whether the given edge is attached to this vertex
*/
bool has_edge (const Edge *edge) const;
/**
* @brief Sets a value indicating whether the vertex is precious
*
* "precious" vertexes are not removed during triangulation for example.
*/
void set_is_precious (bool f, unsigned int id);
/**
* @brief Gets a value indicating whether the vertex is precious
*/
bool is_precious () const;
/**
* @brief Gets the ID passed to "set_is_precious"
*
* This ID can be used to identify the vertex in the context it came from (e.g.
* index in point vector).
*/
const std::set<unsigned int> &ids () const;
/**
* @brief Returns a string representation of the vertex
*/
std::string to_string (bool with_id = false) const;
/**
* @brief Returns 1 is the point is inside the circle, 0 if on the circle and -1 if outside
*/
static int in_circle (const db::DPoint &point, const db::DPoint &center, double radius);
/**
* @brief Returns 1 is this point is inside the circle, 0 if on the circle and -1 if outside
*/
int in_circle (const db::DPoint &center, double radius) const
{
return in_circle (*this, center, radius);
}
protected:
Vertex (Graph *graph);
Vertex (Graph *graph, const DPoint &p);
Vertex (Graph *graph, const Vertex &v);
Vertex (Graph *graph, db::DCoord x, db::DCoord y);
~Vertex ();
private:
friend class Edge;
friend class Graph;
friend class tl::stable_vector<Vertex>;
void remove_edge (const edges_iterator_non_const &ec)
{
mp_edges.erase (ec);
}
Graph *mp_graph;
edges_type mp_edges;
std::set<unsigned int> *mp_ids;
};
/**
* @brief A class representing an edge in the Delaunay triangulation graph
*/
class DB_PUBLIC Edge
{
public:
class PolygonIterator
{
public:
typedef Polygon value_type;
typedef Polygon &reference;
typedef Polygon *pointer;
reference operator*() const
{
return *operator-> ();
}
pointer operator->() const
{
return m_index ? mp_edge->right () : mp_edge->left ();
}
bool operator== (const PolygonIterator &other) const
{
return m_index == other.m_index;
}
bool operator!= (const PolygonIterator &other) const
{
return !operator== (other);
}
PolygonIterator &operator++ ()
{
while (++m_index < 2 && operator-> () == 0)
;
return *this;
}
private:
friend class Edge;
PolygonIterator (const Edge *edge)
: mp_edge (edge), m_index (0)
{
if (! edge) {
m_index = 2;
} else {
--m_index;
operator++ ();
}
}
const Edge *mp_edge;
unsigned int m_index;
};
/**
* @brief Gets the first vertex ("from")
*/
Vertex *v1 () const { return mp_v1; }
/**
* @brief Gets the first vertex ("to")
*/
Vertex *v2 () const { return mp_v2; }
/**
* @brief Reverses the edge
*/
void reverse ()
{
std::swap (mp_v1, mp_v2);
std::swap (mp_left, mp_right);
}
/**
* @brief Gets the polygon on the left side (can be null)
*/
Polygon *left () const { return mp_left; }
/**
* @brief Gets the polygon on the right side (can be null)
*/
Polygon *right () const { return mp_right; }
/**
* @brief Iterates the polygons (one or two, begin iterator)
*/
PolygonIterator begin_polygons () const
{
return PolygonIterator (this);
}
/**
* @brief Iterates the polygons (end iterator)
*/
PolygonIterator end_polygons () const
{
return PolygonIterator (0);
}
/**
* @brief Gets a value indicating whether the edge is a segment
*/
bool is_segment () const { return m_is_segment; }
/**
* @brief Gets the edge ID (a unique identifier)
*/
size_t id () const { return m_id; }
/**
* @brief Gets a string representation of the edge
*/
std::string to_string (bool with_id = false) const;
/**
* @brief Converts to a db::DEdge
*/
db::DEdge edge () const
{
return db::DEdge (*mp_v1, *mp_v2);
}
/**
* @brief Returns the distance of the given point to the edge
*
* The distance is the minimum distance of the point to one point from the edge.
* TODO: Move to db::DEdge
*/
static double distance (const db::DEdge &e, const db::DPoint &p);
/**
* @brief Returns the distance of the given point to the edge
*
* The distance is the minimum distance of the point to one point from the edge.
*/
double distance (const db::DPoint &p) const
{
return distance (edge (), p);
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
*
* "crosses" is true, if both edges share at least one point which is not an endpoint
* of one of the edges.
* TODO: Move to db::DEdge
*/
static bool crosses (const db::DEdge &e, const db::DEdge &other);
/**
* @brief Returns a value indicating whether this edge crosses the other one
*
* "crosses" is true, if both edges share at least one point which is not an endpoint
* of one of the edges.
*/
bool crosses (const db::DEdge &other) const
{
return crosses (edge (), other);
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
*
* "crosses" is true, if both edges share at least one point which is not an endpoint
* of one of the edges.
*/
bool crosses (const Edge &other) const
{
return crosses (edge (), other.edge ());
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
* "crosses" is true, if both edges share at least one point.
* TODO: Move to db::DEdge
*/
static bool crosses_including (const db::DEdge &e, const db::DEdge &other);
/**
* @brief Returns a value indicating whether this edge crosses the other one
* "crosses" is true, if both edges share at least one point.
*/
bool crosses_including (const db::DEdge &other) const
{
return crosses_including (edge (), other);
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
* "crosses" is true, if both edges share at least one point.
*/
bool crosses_including (const Edge &other) const
{
return crosses_including (edge (), other.edge ());
}
/**
* @brief Gets the intersection point
* TODO: Move to db::DEdge
*/
static db::DPoint intersection_point (const db::DEdge &e, const DEdge &other);
/**
* @brief Gets the intersection point
*/
db::DPoint intersection_point (const db::DEdge &other) const
{
return intersection_point (edge (), other);
}
/**
* @brief Gets the intersection point
*/
db::DPoint intersection_point (const Edge &other) const
{
return intersection_point (edge (), other.edge ());
}
/**
* @brief Returns a value indicating whether the point is on the edge
* TODO: Move to db::DEdge
*/
static bool point_on (const db::DEdge &edge, const db::DPoint &point);
/**
* @brief Returns a value indicating whether the point is on the edge
*/
bool point_on (const db::DPoint &point) const
{
return point_on (edge (), point);
}
/**
* @brief Gets the side the point is on
*
* -1 is for "left", 0 is "on" and +1 is "right"
* TODO: correct to same definition as db::Edge (negative)
*/
static int side_of (const db::DEdge &e, const db::DPoint &point)
{
return -e.side_of (point);
}
/**
* @brief Gets the side the point is on
*
* -1 is for "left", 0 is "on" and +1 is "right"
* TODO: correct to same definition as db::Edge (negative)
*/
int side_of (const db::DPoint &p) const
{
return -edge ().side_of (p);
}
/**
* @brief Gets the distance vector
*/
db::DVector d () const
{
return *mp_v2 - *mp_v1;
}
/**
* @brief Gets the other triangle for the given one
*/
Polygon *other (const Polygon *) const;
/**
* @brief Gets the other vertex for the given one
*/
Vertex *other (const Vertex *) const;
/**
* @brief Gets a value indicating whether the edge has the given vertex
*/
bool has_vertex (const Vertex *) const;
/**
* @brief Gets the common vertex of the other edge and this edge or null if there is no common vertex
*/
Vertex *common_vertex (const Edge *other) const;
/**
* @brief Returns a value indicating whether this edge can be flipped
*/
bool can_flip () const;
/**
* @brief Returns a value indicating whether the edge separates two triangles that can be joined into one (via the given vertex)
*/
bool can_join_via (const Vertex *vertex) const;
/**
* @brief Returns a value indicating whether this edge belongs to outside triangles
*/
bool is_for_outside_triangles () const;
/**
* @brief Returns a value indicating whether this edge is an outside edge (no other triangles)
*/
bool is_outside () const;
/**
* @brief Returns a value indicating whether t is attached to this edge
*/
bool has_polygon (const Polygon *t) const;
protected:
void unlink ();
void link ();
void set_level (size_t l) { m_level = l; }
size_t level () const { return m_level; }
void set_id (size_t id) { m_id = id; }
void set_is_segment (bool is_seg) { m_is_segment = is_seg; }
Edge (Graph *graph);
Edge (Graph *graph, Vertex *v1, Vertex *v2);
~Edge ();
private:
friend class Polygon;
friend class Graph;
friend class Triangulation;
friend class ConvexDecomposition;
friend class tl::stable_vector<Edge>;
Graph *mp_graph;
Vertex *mp_v1, *mp_v2;
Polygon *mp_left, *mp_right;
Vertex::edges_iterator_non_const m_ec_v1, m_ec_v2;
size_t m_level;
size_t m_id;
bool m_is_segment;
void set_left (Polygon *t);
void set_right (Polygon *t);
};
/**
* @brief A compare function that compares edges by ID
*
* The ID acts as a more predicable unique ID for the object in sets and maps.
*/
struct EdgeLessFunc
{
bool operator () (Edge *a, Edge *b) const
{
return a->id () < b->id ();
}
};
/**
* @brief A class representing a polygon
*/
class DB_PUBLIC Polygon
: public tl::list_node<Polygon>, public tl::Object
{
public:
/**
* @brief Destructor
*
* It is legal to delete a polygon object to remove it.
*/
~Polygon ();
/**
* @brief Detaches a polygon object from the edges
*/
void unlink ();
/**
* @brief Gets the polygon's unique ID
*/
size_t id () const { return m_id; }
/**
* @brief Gets a value indicating whether the polygon is an outside polygon
*
* Outside polygons are polygons that fill concave parts in a triangulation for example.
*/
bool is_outside () const { return m_is_outside; }
/**
* @brief Returns a string representation
*/
std::string to_string (bool with_id = false) const;
/**
* @brief Gets the number of vertexes
*/
size_t size () const
{
return mp_e.size ();
}
/**
* @brief Gets the internal vertexes
*
* Internal vertexes are special points inside the polygons.
*/
size_t internal_vertexes () const
{
return mp_v.size () - mp_e.size ();
}
/**
* @brief Adds a vertex as an internal vertex
*/
void add_internal_vertex (Vertex *v)
{
mp_v.push_back (v);
}
/**
* @brief Reserves for n internal vertexes
*/
void reserve_internal_vertexes (size_t n)
{
mp_v.reserve (mp_v.size () + n);
}
/**
* @brief Gets the nth vertex (n wraps around and can be negative)
* The vertexes are oriented clockwise.
*/
inline Vertex *vertex (int n) const
{
size_t sz = size ();
tl_assert (sz > 0);
if (n >= 0 && size_t (n) < sz) {
return mp_v[n];
} else {
return mp_v[(n + sz) % sz];
}
}
/**
* @brief Gets the nth internal vertex
*/
inline Vertex *internal_vertex (size_t n) const
{
return mp_v[mp_e.size () + n];
}
/**
* @brief Gets the nth edge (n wraps around and can be negative)
*/
inline Edge *edge (int n) const
{
size_t sz = size ();
tl_assert (sz > 0);
if (n >= 0 && size_t (n) < sz) {
return mp_e[n];
} else {
return mp_e[(n + sz) % sz];
}
}
/**
* @brief Gets the area
*/
double area () const;
/**
* @brief Returns the bounding box of the triangle
*/
db::DBox bbox () const;
/**
* @brief Returns a DPolygon object for this polygon
*/
db::DPolygon polygon () const;
/**
* @brief Gets the center point and radius of the circumcircle
* If ok is non-null, it will receive a boolean value indicating whether the circumcircle is valid.
* An invalid circumcircle is an indicator for a degenerated triangle with area 0 (or close to).
*
* This method only applies to triangles.
*/
std::pair<db::DPoint, double> circumcircle (bool *ok = 0) const;
/**
* @brief Gets the vertex opposite of the given edge
*
* This method only applies to triangles.
*/
Vertex *opposite (const Edge *edge) const;
/**
* @brief Gets the edge opposite of the given vertex
*
* This method only applies to triangles.
*/
Edge *opposite (const Vertex *vertex) const;
/**
* @brief Gets the edge with the given vertexes
*/
Edge *find_edge_with (const Vertex *v1, const Vertex *v2) const;
/**
* @brief Finds the common edge for both polygons
*/
Edge *common_edge (const Polygon *other) const;
/**
* @brief Returns a value indicating whether the point is inside (1), on the polygon (0) or outside (-1)
*
* This method only applies to triangles currently.
*/
int contains (const db::DPoint &point) const;
/**
* @brief Gets a value indicating whether the triangle has the given vertex
*/
inline bool has_vertex (const Vertex *v) const
{
for (auto i = mp_v.begin (); i != mp_v.end (); ++i) {
if (*i == v) {
return true;
}
}
return false;
}
/**
* @brief Gets a value indicating whether the triangle has the given edge
*/
inline bool has_edge (const Edge *e) const
{
for (auto i = mp_e.begin (); i != mp_e.end (); ++i) {
if (*i == e) {
return true;
}
}
return false;
}
/**
* @brief Coming from an edge e and the vertex v, gets the next edge
*/
Edge *next_edge (const Edge *e, const Vertex *v) const;
/**
* @brief Returns the minimum edge length
*/
double min_edge_length () const;
/**
* @brief Returns the min edge length to circumcircle radius ratio
*
* This method only applies to triangles currently.
*/
double b () const;
/**
* @brief Returns a value indicating whether the polygon borders to a segment
*/
bool has_segment () const;
/**
* @brief Returns the number of segments the polygon borders to
*/
unsigned int num_segments () const;
protected:
Polygon (Graph *graph);
Polygon (Graph *graph, Edge *e1, Edge *e2, Edge *e3);
template<class Iter>
Polygon (Graph *graph, Iter from, Iter to)
: mp_graph (graph), mp_e (from, to)
{
init ();
}
void set_outside (bool o) { m_is_outside = o; }
void set_id (size_t id) { m_id = id; }
private:
friend class Graph;
friend class Triangulation;
Graph *mp_graph;
bool m_is_outside;
std::vector<Edge *> mp_e;
std::vector<Vertex *> mp_v;
size_t m_id;
void init ();
// no copying
Polygon &operator= (const Polygon &);
Polygon (const Polygon &);
};
/**
* @brief A compare function that compares polygons by ID
*
* The ID acts as a more predicable unique ID for the object in sets and maps.
*/
struct PolygonLessFunc
{
bool operator () (Polygon *a, Polygon *b) const
{
return a->id () < b->id ();
}
};
/**
* @brief A class representing the polygon graph
*
* A polygon graph is the main container, holding vertexes, edges and polygons.
* The graph can be of "triangles" type, in which case it is guaranteed to only
* hold triangles (polygons with 3 vertexes).
*/
class DB_PUBLIC Graph
: public tl::Object
{
public:
typedef tl::list<Polygon> polygons_type;
typedef polygons_type::const_iterator polygon_iterator;
Graph ();
~Graph ();
/**
* @brief Returns a string representation of the polygon graph.
*/
std::string to_string ();
/**
* @brief Returns the bounding box of the polygon graph.
*/
db::DBox bbox () const;
/**
* @brief Iterates the polygons in the graph (begin iterator)
*/
polygon_iterator begin () const { return mp_polygons.begin (); }
/**
* @brief Iterates the polygons in the graph (end iterator)
*/
polygon_iterator end () const { return mp_polygons.end (); }
/**
* @brief Returns the number of polygons in the graph
*/
size_t num_polygons () const { return mp_polygons.size (); }
/**
* @brief Clears the polygon set
*/
void clear ();
/**
* @brief Dumps the polygon graph to a GDS file at the given path
* This method is for testing purposes mainly.
*
* "decompose_id" will map polygons to layer 20, 21 and 22.
* according to bit 0, 1 and 2 of the ID (useful with the 'mark_polygons'
* flat in TriangulateParameters).
*/
void dump (const std::string &path, bool decompose_by_id = false) const;
/**
* @brief Creates a new layout object representing the polygon graph
* This method is for testing purposes mainly.
*/
db::Layout *to_layout (bool decompose_by_id = false) const;
protected:
Vertex *create_vertex (double x, double y);
Vertex *create_vertex (const db::DPoint &pt);
Edge *create_edge (Vertex *v1, Vertex *v2);
template <class Iter>
Polygon *
create_polygon (Iter from, Iter to)
{
Polygon *res = new Polygon (this, from ,to);
res->set_id (++m_id);
mp_polygons.push_back (res);
return res;
}
Polygon *create_triangle (Edge *e1, Edge *e2, Edge *e3);
void remove_polygon (Polygon *p);
private:
friend class Triangulation;
friend class ConvexDecomposition;
tl::list<Polygon> mp_polygons;
tl::stable_vector<Edge> m_edges_heap;
std::vector<Edge *> m_returned_edges;
tl::stable_vector<Vertex> m_vertex_heap;
size_t m_id;
tl::list<Polygon> &polygons () { return mp_polygons; }
tl::stable_vector<Edge> &edges () { return m_edges_heap; }
tl::stable_vector<Vertex> &vertexes () { return m_vertex_heap; }
};
} // namespace plc
} // namespace db
#endif

View File

@ -0,0 +1,491 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbPLCConvexDecomposition.h"
#include "dbPLCTriangulation.h"
#include "tlLog.h"
#include "tlTimer.h"
#include <set>
#include <memory>
#include <vector>
#include <map>
namespace db
{
namespace plc
{
ConvexDecomposition::ConvexDecomposition (Graph *graph)
{
mp_graph = graph;
clear ();
}
void
ConvexDecomposition::clear ()
{
mp_graph->clear ();
}
struct SortAngleAndEdgesByEdgeLength
{
typedef std::list<std::pair<double, const Edge *> > angle_and_edges_list;
bool operator() (const angle_and_edges_list::iterator &a, const angle_and_edges_list::iterator &b) const
{
double la = a->second->edge ().double_sq_length ();
double lb = b->second->edge ().double_sq_length ();
if (fabs (la - lb) > db::epsilon) {
return la < lb;
} else {
return a->second->edge ().less (b->second->edge ());
}
}
};
// TODO: move to some generic header
template <class T>
struct less_compare_func
{
bool operator() (const T &a, const T &b) const
{
return a.less (b);
}
};
// TODO: move to some generic header
template <class T>
struct equal_compare_func
{
bool operator() (const T &a, const T &b) const
{
return a.equal (b);
}
};
Edge *find_outgoing_segment (Vertex *vertex, Edge *incoming, int &vp_max_sign)
{
Vertex *vfrom = incoming->other (vertex);
db::DEdge e1 (*vfrom, *vertex);
double vp_max = 0.0;
vp_max_sign = 0;
Edge *outgoing = 0;
// Look for the outgoing edge. We pick the one which bends "most", favoring
// convex corners. Multiple edges per vertex are possible is corner cases such as the
// "hourglass" configuration.
for (auto e = vertex->begin_edges (); e != vertex->end_edges (); ++e) {
Edge *en = *e;
if (en != incoming && en->is_segment ()) {
Vertex *v = en->other (vertex);
db::DEdge e2 (*vertex, *v);
double vp = double (db::vprod (e1, e2)) / (e1.double_length () * e2.double_length ());
// vp > 0: concave, vp < 0: convex
if (! outgoing || vp > vp_max) {
vp_max_sign = db::vprod_sign (e1, e2);
vp_max = vp;
outgoing = en;
}
}
}
tl_assert (outgoing != 0);
return outgoing;
}
void
ConvexDecomposition::collect_concave_vertexes (std::vector<ConcaveCorner> &concave_vertexes)
{
concave_vertexes.clear ();
std::unordered_set<Edge *> left;
for (auto e = mp_graph->edges ().begin (); e != mp_graph->edges ().end (); ++e) {
if (e->is_segment () && (e->left () != 0 || e->right () != 0)) {
left.insert (e.operator-> ());
}
}
while (! left.empty ()) {
// First segment for a new loop
Edge *segment = *left.begin ();
// walk along the segments in clockwise direction. Find concave
// vertexes and create new vertexes perpendicular to the incoming
// and outgoing edge.
Edge *start_segment = segment;
Vertex *vto = (segment->right () && ! segment->right ()->is_outside ()) ? segment->v2 () : segment->v1 ();
do {
left.erase (segment);
Edge *prev_segment = segment;
int vp_sign = 0;
segment = find_outgoing_segment (vto, prev_segment, vp_sign);
if (vp_sign > 0) {
concave_vertexes.push_back (ConcaveCorner (vto, prev_segment, segment));
}
vto = segment->other (vto);
} while (segment != start_segment);
}
}
std::pair<bool, db::DPoint>
ConvexDecomposition::search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction)
{
auto vtri = v0->polygons (); // TODO: slow?
std::vector<const Vertex *> nvv, nvv_next;
for (auto it = vtri.begin (); it != vtri.end (); ++it) {
// Search for a segment in the direction perpendicular to the edge
nvv.clear ();
nvv.push_back (v0);
const Polygon *t = *it;
while (! nvv.empty ()) {
nvv_next.clear ();
for (auto iv = nvv.begin (); iv != nvv.end (); ++iv) {
const Vertex *v = *iv;
const Edge *oe = t->opposite (v);
const Polygon *tt = oe->other (t);
const Vertex *v1 = oe->v1 ();
const Vertex *v2 = oe->v2 ();
if (db::sprod_sign (*v2 - *v, direction) >= 0 && db::sprod_sign (*v1 - *v, direction) >= 0 &&
db::vprod_sign (*v2 - *v, direction) * db::vprod_sign (*v1 - *v, direction) < 0) {
// this triangle covers the normal vector of e1 -> stop here or continue searching in that direction
if (oe->is_segment ()) {
auto cp = oe->edge ().cut_point (db::DEdge (*v0, *v0 + direction));
if (cp.first) {
return std::make_pair (true, cp.second);
}
} else {
// continue searching in that direction
nvv_next.push_back (v1);
nvv_next.push_back (v2);
t = tt;
}
break;
}
}
nvv.swap (nvv_next);
}
}
return std::make_pair (false, db::DPoint ());
}
void
ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const ConvexDecompositionParameters &param)
{
bool with_segments = param.with_segments;
bool split_edges = param.split_edges;
std::vector<ConcaveCorner> concave_vertexes;
collect_concave_vertexes (concave_vertexes);
std::vector<db::DPoint> new_points;
if (with_segments) {
// Create internal segments cutting off pieces orthogonal to the edges
// connecting the concave vertex.
for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) {
for (unsigned int ei = 0; ei < 2; ++ei) {
db::DEdge ee;
const Vertex *v0 = cc->corner;
if (ei == 0) {
ee = db::DEdge (*cc->incoming->other (v0), *v0);
} else {
ee = db::DEdge (*v0, *cc->outgoing->other (v0));
}
auto cp = search_crossing_with_next_segment (v0, db::DVector (ee.dy (), -ee.dx ()));
if (cp.first) {
new_points.push_back (cp.second);
}
}
}
}
// eliminate duplicates and put the new points in some order
if (! new_points.empty ()) {
std::sort (new_points.begin (), new_points.end (), less_compare_func<db::DPoint> ());
new_points.erase (std::unique (new_points.begin (), new_points.end (), equal_compare_func<db::DPoint> ()), new_points.end ());
// Insert the new points and make connections
for (auto p = new_points.begin (); p != new_points.end (); ++p) {
tris.insert_point (*p);
}
// As the insertion invalidates the edges, we need to collect the concave vertexes again
collect_concave_vertexes (concave_vertexes);
}
// Collect essential edges
// Every concave vertex can have up to two essential edges.
// Other then suggested by Hertel-Mehlhorn we don't pick
// them one-by-one, but using them in length order, from the
std::unordered_set<const Edge *> essential_edges;
typedef std::list<std::pair<double, const Edge *> > angles_and_edges_list;
angles_and_edges_list angles_and_edges;
std::vector<angles_and_edges_list::iterator> sorted_edges;
for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) {
angles_and_edges.clear ();
const Vertex *v0 = cc->corner;
const Edge *e = cc->incoming;
while (e) {
const Polygon *t = e->v2 () == v0 ? e->right () : e->left ();
tl_assert (t != 0);
const Edge *en = t->next_edge (e, v0);
tl_assert (en != 0);
db::DVector v1 = e->edge ().d () * (e->v1 () == v0 ? 1.0 : -1.0);
db::DVector v2 = en->edge ().d () * (en->v1 () == v0 ? 1.0 : -1.0);
double angle = atan2 (db::vprod (v1, v2), db::sprod (v1, v2));
e = (en == cc->outgoing) ? 0 : en;
angles_and_edges.push_back (std::make_pair (angle, e));
}
sorted_edges.clear ();
for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) {
if (i->second) {
sorted_edges.push_back (i);
}
}
std::sort (sorted_edges.begin (), sorted_edges.end (), SortAngleAndEdgesByEdgeLength ());
for (auto i = sorted_edges.end (); i != sorted_edges.begin (); ) {
--i;
angles_and_edges_list::iterator ii = *i;
angles_and_edges_list::iterator iin = ii;
++iin;
if (ii->first + iin->first < (split_edges ? M_PI + db::epsilon : M_PI - db::epsilon)) {
// not an essential edge -> remove
iin->first += ii->first;
angles_and_edges.erase (ii);
}
}
for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) {
if (i->second) {
essential_edges.insert (i->second);
}
}
}
// Combine triangles, but don't cross essential edges
std::unordered_set<const Polygon *> left_triangles;
for (auto it = mp_graph->begin (); it != mp_graph->end (); ++it) {
if (! it->is_outside ()) {
left_triangles.insert (it.operator-> ());
}
}
std::list<std::unordered_set<Edge *> > polygons;
std::list<std::unordered_set<Vertex *> > internal_vertexes;
while (! left_triangles.empty ()) {
polygons.push_back (std::unordered_set<Edge *> ());
std::unordered_set<Edge *> &edges = polygons.back ();
internal_vertexes.push_back (std::unordered_set<Vertex *> ());
std::unordered_set<Vertex *> &ivs = internal_vertexes.back ();
const Polygon *tri = *left_triangles.begin ();
std::vector<const Polygon *> queue, next_queue;
queue.push_back (tri);
while (! queue.empty ()) {
next_queue.clear ();
for (auto q = queue.begin (); q != queue.end (); ++q) {
left_triangles.erase (*q);
for (unsigned int i = 0; i < 3; ++i) {
const Edge *e = (*q)->edge (i);
const Polygon *qq = e->other (*q);
if (e->v1 ()->is_precious ()) {
ivs.insert (e->v1 ());
}
if (e->v2 ()->is_precious ()) {
ivs.insert (e->v2 ());
}
if (! qq || qq->is_outside () || essential_edges.find (e) != essential_edges.end ()) {
edges.insert (const_cast<Edge *> (e)); // TODO: ugly const_cast
} else if (left_triangles.find (qq) != left_triangles.end ()) {
next_queue.push_back (qq);
}
}
}
queue.swap (next_queue);
}
}
// remove the triangles
while (mp_graph->begin () != mp_graph->end ()) {
delete mp_graph->begin ().operator-> ();
}
// create the polygons
auto iv = internal_vertexes.begin ();
for (auto p = polygons.begin (); p != polygons.end (); ++p) {
Polygon *poly = mp_graph->create_polygon (p->begin (), p->end ());
poly->reserve_internal_vertexes (iv->size ());
for (auto i = iv->begin (); i != iv->end (); ++i) {
poly->add_internal_vertex (*i);
}
++iv;
}
}
void
ConvexDecomposition::decompose (const db::Polygon &poly, const ConvexDecompositionParameters &parameters, double dbu)
{
decompose (poly, parameters, db::CplxTrans (dbu));
}
void
ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const ConvexDecompositionParameters &parameters, double dbu)
{
decompose (poly, vertexes, parameters, db::CplxTrans (dbu));
}
void
ConvexDecomposition::decompose (const db::Polygon &poly, const ConvexDecompositionParameters &parameters, const db::CplxTrans &trans)
{
Triangulation tri (mp_graph);
tri.triangulate (poly, parameters.tri_param, trans);
hertel_mehlhorn_decomposition (tri, parameters);
}
void
ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const ConvexDecompositionParameters &parameters, const db::CplxTrans &trans)
{
Triangulation tri (mp_graph);
tri.triangulate (poly, vertexes, parameters.tri_param, trans);
hertel_mehlhorn_decomposition (tri, parameters);
}
void
ConvexDecomposition::decompose (const db::DPolygon &poly, const ConvexDecompositionParameters &parameters, const db::DCplxTrans &trans)
{
Triangulation tri (mp_graph);
tri.triangulate (poly, parameters.tri_param, trans);
hertel_mehlhorn_decomposition (tri, parameters);
}
void
ConvexDecomposition::decompose (const db::DPolygon &poly, const std::vector<db::DPoint> &vertexes, const ConvexDecompositionParameters &parameters, const db::DCplxTrans &trans)
{
Triangulation tri (mp_graph);
tri.triangulate (poly, vertexes, parameters.tri_param, trans);
hertel_mehlhorn_decomposition (tri, parameters);
}
void
ConvexDecomposition::decompose (const db::Region &region, const ConvexDecompositionParameters &parameters, double dbu)
{
decompose (region, parameters, db::CplxTrans (dbu));
}
void
ConvexDecomposition::decompose (const db::Region &region, const ConvexDecompositionParameters &parameters, const db::CplxTrans &trans)
{
Triangulation tri (mp_graph);
tri.triangulate (region, parameters.tri_param, trans);
hertel_mehlhorn_decomposition (tri, parameters);
}
} // namespace plc
} // namespace db

View File

@ -0,0 +1,162 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_dbPLCConvexDecomposition
#define HDR_dbPLCConvexDecomposition
#include "dbCommon.h"
#include "dbPLC.h"
#include "dbPLCTriangulation.h"
#include <limits>
#include <list>
#include <vector>
#include <algorithm>
namespace db
{
namespace plc
{
struct DB_PUBLIC ConvexDecompositionParameters
{
ConvexDecompositionParameters ()
: with_segments (false),
split_edges (false),
base_verbosity (30)
{
tri_param.max_area = 0.0;
tri_param.min_b = 0.0;
// Needed for the algorithm - don't change this
tri_param.remove_outside_triangles = false;
}
/**
* @brief The parameters used for the triangulation
*/
TriangulationParameters tri_param;
/**
* @brief Introduce new segments
*
* If true, new segments will be introduced.
* New segments are constructed perpendicular to the edges forming
* a concave corner.
*/
bool with_segments;
/**
* @brief Split edges
*
* If true, edges in the resulting polygons may be split.
* This will produce edge sections that correlate with
* other polygon edges, but may be collinear with neighbor
* edges.
*/
double split_edges;
/**
* @brief The verbosity level above which triangulation reports details
*/
int base_verbosity;
};
/**
* @brief A convex decomposition algorithm
*
* This class implements a variant of the Hertel-Mehlhorn decomposition.
*/
class DB_PUBLIC ConvexDecomposition
{
public:
/**
* @brief The constructor
*
* The graph will be one filled by the decomposition.
*/
ConvexDecomposition (Graph *graph);
/**
* @brief Clears the triangulation
*/
void clear ();
/**
* @brief Creates a decomposition for the given region
*
* The database unit should be chosen in a way that target area values are "in the order of 1".
* For inputs featuring acute angles (angles < ~25 degree), the parameters should defined a min
* edge length ("min_length").
* "min_length" should be at least 1e-4. If a min edge length is given, the max area constaints
* may not be satisfied.
*
* Edges in the input should not be shorter than 1e-4.
*/
void decompose (const db::Region &region, const ConvexDecompositionParameters &parameters, double dbu = 1.0);
// more versions
void decompose (const db::Region &region, const ConvexDecompositionParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
void decompose (const db::Polygon &poly, const ConvexDecompositionParameters &parameters, double dbu = 1.0);
void decompose (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const ConvexDecompositionParameters &parameters, double dbu = 1.0);
void decompose (const db::Polygon &poly, const ConvexDecompositionParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
void decompose (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const ConvexDecompositionParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
/**
* @brief Decomposes a floating-point polygon
*/
void decompose (const db::DPolygon &poly, const ConvexDecompositionParameters &parameters, const db::DCplxTrans &trans = db::DCplxTrans ());
void decompose (const db::DPolygon &poly, const std::vector<db::DPoint> &vertexes, const ConvexDecompositionParameters &parameters, const db::DCplxTrans &trans = db::DCplxTrans ());
private:
Graph *mp_graph;
struct ConcaveCorner
{
ConcaveCorner ()
: corner (0), incoming (0), outgoing (0)
{
// .. nothing yet ..
}
ConcaveCorner (Vertex *_corner, Edge *_incoming, Edge *_outgoing)
: corner (_corner), incoming (_incoming), outgoing (_outgoing)
{
// .. nothing yet ..
}
Vertex *corner;
Edge *incoming, *outgoing;
};
void hertel_mehlhorn_decomposition (Triangulation &tris, const ConvexDecompositionParameters &param);
void collect_concave_vertexes (std::vector<ConcaveCorner> &concave_vertexes);
std::pair<bool, db::DPoint> search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction);
};
} // namespace plc
} // namespace db
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,357 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_dbPLCTriangulation
#define HDR_dbPLCTriangulation
#include "dbCommon.h"
#include "dbPLC.h"
#include <limits>
#include <list>
#include <vector>
#include <algorithm>
namespace db
{
namespace plc
{
struct DB_PUBLIC TriangulationParameters
{
TriangulationParameters ()
: min_b (1.0),
min_length (0.0),
max_area (0.0),
max_area_border (0.0),
max_iterations (std::numeric_limits<size_t>::max ()),
base_verbosity (30),
mark_triangles (false),
remove_outside_triangles (true)
{ }
/**
* @brief Min. readius-to-shortest edge ratio
*/
double min_b;
/**
* @brief Min. edge length
*
* This parameter does not provide a guarantee about a minimume edge length, but
* helps avoiding ever-reducing triangle splits in acute corners of the input polygon.
* Splitting of edges stops when the edge is less than the min length.
*/
double min_length;
/**
* @brief Max area or zero for "no constraint"
*/
double max_area;
/**
* @brief Max area for border triangles or zero for "use max_area"
*/
double max_area_border;
/**
* @brief Max number of iterations
*/
size_t max_iterations;
/**
* @brief The verbosity level above which triangulation reports details
*/
int base_verbosity;
/**
* @brief If true, final triangles are marked using the "id" integer as a bit field
*
* This provides information about the result quality.
*
* Bit 0: skinny triangle
* Bit 1: bad-quality (skinny or area too large)
* Bit 2: non-Delaunay (in the strict sense)
*/
bool mark_triangles;
/**
* @brief If false, the outside triangles are not removed after triangulation
*/
bool remove_outside_triangles;
};
/**
* @brief A Triangulation algorithm
*
* This class implements a constrained refined Delaunay triangulation using Chew's algorithm.
*/
class DB_PUBLIC Triangulation
{
public:
/**
* @brief The constructor
*
* The graph will be one filled by the triangulation.
*/
Triangulation (Graph *graph);
/**
* @brief Clears the triangulation
*/
void clear ();
/**
* @brief Initializes the triangle collection with a box
* Two triangles will be created.
*/
void init_box (const db::DBox &box);
/**
* @brief Creates a refined Delaunay triangulation for the given region
*
* The database unit should be chosen in a way that target area values are "in the order of 1".
* For inputs featuring acute angles (angles < ~25 degree), the parameters should defined a min
* edge length ("min_length").
* "min_length" should be at least 1e-4. If a min edge length is given, the max area constaints
* may not be satisfied.
*
* Edges in the input should not be shorter than 1e-4.
*/
void triangulate (const db::Region &region, const TriangulationParameters &parameters, double dbu = 1.0);
// more versions
void triangulate (const db::Region &region, const TriangulationParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
void triangulate (const db::Region &region, const std::vector<db::Point> &vertexes, const TriangulationParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
void triangulate (const db::Polygon &poly, const TriangulationParameters &parameters, double dbu = 1.0);
void triangulate (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const TriangulationParameters &parameters, double dbu = 1.0);
void triangulate (const db::Polygon &poly, const TriangulationParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
void triangulate (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const TriangulationParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
/**
* @brief Triangulates a floating-point polygon
*/
void triangulate (const db::DPolygon &poly, const TriangulationParameters &parameters, const db::DCplxTrans &trans = db::DCplxTrans ());
void triangulate (const db::DPolygon &poly, const std::vector<db::DPoint> &vertexes, const TriangulationParameters &parameters, const db::DCplxTrans &trans = db::DCplxTrans ());
/**
* @brief Inserts a new vertex as the given point
*
* If "new_triangles" is not null, it will receive the list of new triangles created during
* the remove step.
*
* This method can be called after "triangulate" to add new points and adjust the triangulation.
* Inserting new points will maintain the (constrained) Delaunay condition.
*/
Vertex *insert_point (const db::DPoint &point, std::list<tl::weak_ptr<Polygon> > *new_triangles = 0);
/**
* @brief Finds the edge for two given points
*/
Edge *find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) const;
/**
* @brief Finds the vertex for a point
*/
Vertex *find_vertex_for_point (const db::DPoint &pt) const;
/**
* @brief Finds the vertexes along the line given from p1 and p2
*
* At least one of the points p1 and p2 must be existing vertexes.
*/
std::vector<Vertex *> find_vertexes_along_line (const db::DPoint &p1, const db::DPoint &p2) const;
/**
* @brief Removes the outside triangles.
*
* This method is useful in combination with the "remove_outside_triangles = false" triangulation
* parameter. In this mode, outside triangles are not removed after triangulation (the
* triangulated area is convex). This enables use of the "find" functions.
*
* This method can be used to finally remove the outside triangles if no longer needed.
*/
void remove_outside_triangles ();
/**
* @brief Statistics: number of flips (fixing)
*/
size_t flips () const
{
return m_flips;
}
/**
* @brief Statistics: number of hops (searching)
*/
size_t hops () const
{
return m_hops;
}
/**
* @brief Creates a constrained Delaunay triangulation from the given Region
*
* This method is used internally by the "triangulation" method to create the basic triangulation,
* followed by a "refine" step.
*/
void create_constrained_delaunay (const db::Region &region, const db::CplxTrans &trans = db::CplxTrans ());
/**
* @brief Creates a constrained Delaunay triangulation from the given Polygon
*
* This method is used internally by the "triangulation" method to create the basic triangulation,
* followed by a "refine" step.
*/
void create_constrained_delaunay (const db::Polygon &poly, const db::CplxTrans &trans = db::CplxTrans ());
/**
* @brief Creates a constrained Delaunay triangulation from the given DPolygon
*
* This method is used internally by the "triangulation" method to create the basic triangulation,
* followed by a "refine" step.
*/
void create_constrained_delaunay (const db::DPolygon &poly, const DCplxTrans &trans = db::DCplxTrans ());
/**
* @brief Refines the triangulation using the given parameters
*
* This method is used internally by the "triangulate" method after creating the basic triangulation.
*
* This method is provided as a partial solution of a triangulation for special cases.
*/
void refine (const TriangulationParameters &param);
/**
* @brief Given a set of contours with edges, mark outer triangles
*
* The edges must be made from existing vertexes. Edge orientation is
* clockwise.
*
* This will also mark triangles as outside ones.
* This method is used internally by the "triangulate" method after creating the basic triangulation.
*
* This method is provided as a partial solution of a triangulation for special cases.
*/
void constrain (const std::vector<std::vector<Vertex *> > &contours);
/**
* @brief Inserts a contours of a polygon
*
* This method fills the contours of the given polygon by doint an "insert_point"
* on all points and logging the outer edges ("segments") into the "contours"
* array. The latter can be passed to "constrain" to create a constrained
* triangulation.
*
* This method is used internally by the "triangulate" method to create the basic triangulation.
* This method is provided as a partial solution of a triangulation for special cases.
*/
template<class Poly, class Trans> void make_contours (const Poly &poly, const Trans &trans, std::vector<std::vector<Vertex *> > &contours);
protected:
/**
* @brief Checks the polygon graph for consistency
* This method is for testing purposes mainly.
*/
bool check (bool check_delaunay = true) const;
/**
* @brief Finds the points within (not "on") a circle of radius "radius" around the given vertex.
*/
std::vector<Vertex *> find_points_around (Vertex *vertex, double radius);
/**
* @brief Inserts a new vertex as the given point
*
* If "new_triangles" is not null, it will receive the list of new triangles created during
* the remove step.
*/
Vertex *insert_point (db::DCoord x, db::DCoord y, std::list<tl::weak_ptr<Polygon> > *new_triangles = 0);
/**
* @brief Removes the given vertex
*
* If "new_triangles" is not null, it will receive the list of new triangles created during
* the remove step.
*/
void remove (Vertex *vertex, std::list<tl::weak_ptr<Polygon> > *new_triangles = 0);
/**
* @brief Flips the given edge
*/
std::pair<std::pair<Polygon *, Polygon *>, Edge *> flip (Edge *edge);
/**
* @brief Finds all edges that cross the given one for a convex triangulation
*
* Requirements:
* * self must be a convex triangulation
* * edge must not contain another vertex from the triangulation except p1 and p2
*/
std::vector<Edge *> search_edges_crossing (Vertex *from, Vertex *to);
/**
* @brief Ensures all points between from an to are connected by edges and makes these segments
*/
std::vector<Edge *> ensure_edge (Vertex *from, Vertex *to);
/**
* @brief Returns a value indicating whether the edge is "illegal" (violates the Delaunay criterion)
*/
static bool is_illegal_edge (Edge *edge);
// NOTE: these functions are SLOW and intended to test purposes only
std::vector<Vertex *> find_touching (const db::DBox &box) const;
std::vector<Vertex *> find_inside_circle (const db::DPoint &center, double radius) const;
private:
Graph *mp_graph;
bool m_is_constrained;
size_t m_level;
size_t m_id;
mutable size_t m_flips, m_hops;
void remove_outside_vertex (Vertex *vertex, std::list<tl::weak_ptr<Polygon> > *new_triangles = 0);
void remove_inside_vertex (Vertex *vertex, std::list<tl::weak_ptr<Polygon> > *new_triangles_out = 0);
std::vector<Polygon *> fill_concave_corners (const std::vector<Edge *> &edges);
void fix_triangles (const std::vector<Polygon *> &tris, const std::vector<Edge *> &fixed_edges, std::list<tl::weak_ptr<Polygon> > *new_triangles);
std::vector<Polygon *> find_triangle_for_point (const db::DPoint &point);
Edge *find_closest_edge (const db::DPoint &p, Vertex *vstart = 0, bool inside_only = false) const;
Vertex *insert (Vertex *vertex, std::list<tl::weak_ptr<Polygon> > *new_triangles = 0);
void split_triangle (Polygon *t, Vertex *vertex, std::list<tl::weak_ptr<Polygon> > *new_triangles_out);
void split_triangles_on_edge (Vertex *vertex, Edge *split_edge, std::list<tl::weak_ptr<Polygon> > *new_triangles_out);
void add_more_triangles (std::vector<Polygon *> &new_triangles,
Edge *incoming_edge,
Vertex *from_vertex, Vertex *to_vertex,
Edge *conn_edge);
void insert_new_vertex(Vertex *vertex, std::list<tl::weak_ptr<Polygon> > *new_triangles_out);
std::vector<Edge *> ensure_edge_inner (Vertex *from, Vertex *to);
void join_edges (std::vector<Edge *> &edges);
};
} // namespace plc
} // namespace db
#endif

View File

@ -373,7 +373,7 @@ PropertiesRepository::prop_name_id (const tl::Variant &name)
}
}
property_names_id_type
property_values_id_type
PropertiesRepository::prop_value_id (const tl::Variant &value)
{
tl::MutexLocker locker (&m_lock);
@ -383,9 +383,9 @@ PropertiesRepository::prop_value_id (const tl::Variant &value)
m_property_values_heap.push_back (value);
const tl::Variant &new_value = m_property_values_heap.back ();
m_propvalues.insert (&new_value);
return property_names_id_type (&new_value);
return property_values_id_type (&new_value);
} else {
return property_names_id_type (*pi);
return property_values_id_type (*pi);
}
}

View File

@ -358,7 +358,7 @@ public:
* This method will assign a new ID to the given value if required and
* return the ID associated with it.
*/
property_names_id_type prop_value_id (const tl::Variant &name);
property_values_id_type prop_value_id (const tl::Variant &name);
/**
* @brief Get the ID for a name

View File

@ -453,8 +453,8 @@ TriangulationProcessor::process (const db::Polygon &poly, std::vector<db::Polygo
// NOTE: we center the polygon for better numerical stability
db::CplxTrans trans = db::CplxTrans (triangulation_dbu) * db::ICplxTrans (db::Trans (db::Point () - poly.box ().center ()));
db::Triangles tri;
tri.triangulate (poly, m_param, trans);
db::plc::Graph tri;
db::plc::Triangulation (&tri).triangulate (poly, m_param, trans);
db::Point pts [3];
auto trans_inv = trans.inverted ();
@ -474,8 +474,8 @@ TriangulationProcessor::process (const db::PolygonWithProperties &poly, std::vec
// NOTE: we center the polygon for better numerical stability
db::CplxTrans trans = db::CplxTrans (triangulation_dbu) * db::ICplxTrans (db::Trans (db::Point () - poly.box ().center ()));
db::Triangles tri;
tri.triangulate (poly, m_param, trans);
db::plc::Graph tri;
db::plc::Triangulation (&tri).triangulate (poly, m_param, trans);
db::Point pts [3];
auto trans_inv = trans.inverted ();

View File

@ -28,7 +28,7 @@
#include "dbRegionDelegate.h"
#include "dbPolygonTools.h"
#include "dbEdgesUtils.h"
#include "dbTriangles.h"
#include "dbPLCTriangulation.h"
#include "dbEdgePairRelations.h"
namespace db
@ -497,7 +497,7 @@ public:
virtual bool wants_variants () const { return true; }
private:
db::Triangles::TriangulateParameters m_param;
db::plc::TriangulationParameters m_param;
db::MagnificationReducer m_vars;
};

View File

@ -1,607 +0,0 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbTriangle.h"
#include <set>
namespace db
{
// -------------------------------------------------------------------------------------
// Vertex implementation
Vertex::Vertex ()
: DPoint (), m_is_precious (false)
{
// .. nothing yet ..
}
Vertex::Vertex (const db::DPoint &p)
: DPoint (p), m_is_precious (false)
{
// .. nothing yet ..
}
Vertex::Vertex (const Vertex &v)
: DPoint (), m_is_precious (false)
{
operator= (v);
}
Vertex &Vertex::operator= (const Vertex &v)
{
if (this != &v) {
// NOTE: edges are not copied!
db::DPoint::operator= (v);
m_is_precious = v.m_is_precious;
}
return *this;
}
Vertex::Vertex (db::DCoord x, db::DCoord y)
: DPoint (x, y), m_is_precious (false)
{
// .. nothing yet ..
}
bool
Vertex::is_outside () const
{
for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) {
if ((*e)->is_outside ()) {
return true;
}
}
return false;
}
std::vector<db::Triangle *>
Vertex::triangles () const
{
std::set<db::Triangle *> seen;
std::vector<db::Triangle *> res;
for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) {
for (auto t = (*e)->begin_triangles (); t != (*e)->end_triangles (); ++t) {
if (seen.insert (t.operator-> ()).second) {
res.push_back (t.operator-> ());
}
}
}
return res;
}
bool
Vertex::has_edge (const TriangleEdge *edge) const
{
for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) {
if (*e == edge) {
return true;
}
}
return false;
}
size_t
Vertex::num_edges (int max_count) const
{
if (max_count < 0) {
// NOTE: this can be slow for a std::list, so we have max_count to limit this effort
return mp_edges.size ();
} else {
size_t n = 0;
for (auto i = mp_edges.begin (); i != mp_edges.end () && --max_count >= 0; ++i) {
++n;
}
return n;
}
}
std::string
Vertex::to_string (bool with_id) const
{
std::string res = tl::sprintf ("(%.12g, %.12g)", x (), y());
if (with_id) {
res += tl::sprintf ("[%x]", (size_t)this);
}
return res;
}
int
Vertex::in_circle (const DPoint &point, const DPoint &center, double radius)
{
double dx = point.x () - center.x ();
double dy = point.y () - center.y ();
double d2 = dx * dx + dy * dy;
double r2 = radius * radius;
double delta = fabs (d2 + r2) * db::epsilon;
if (d2 < r2 - delta) {
return 1;
} else if (d2 < r2 + delta) {
return 0;
} else {
return -1;
}
}
// -------------------------------------------------------------------------------------
// TriangleEdge implementation
TriangleEdge::TriangleEdge ()
: mp_v1 (0), mp_v2 (0), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false)
{
// .. nothing yet ..
}
TriangleEdge::TriangleEdge (Vertex *v1, Vertex *v2)
: mp_v1 (v1), mp_v2 (v2), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false)
{
// .. nothing yet ..
}
void
TriangleEdge::set_left (Triangle *t)
{
mp_left = t;
}
void
TriangleEdge::set_right (Triangle *t)
{
mp_right = t;
}
void
TriangleEdge::link ()
{
mp_v1->mp_edges.push_back (this);
m_ec_v1 = --mp_v1->mp_edges.end ();
mp_v2->mp_edges.push_back (this);
m_ec_v2 = --mp_v2->mp_edges.end ();
}
void
TriangleEdge::unlink ()
{
if (mp_v1) {
mp_v1->remove_edge (m_ec_v1);
}
if (mp_v2) {
mp_v2->remove_edge (m_ec_v2);
}
mp_v1 = mp_v2 = 0;
}
Triangle *
TriangleEdge::other (const Triangle *t) const
{
if (t == mp_left) {
return mp_right;
}
if (t == mp_right) {
return mp_left;
}
tl_assert (false);
return 0;
}
Vertex *
TriangleEdge::other (const Vertex *t) const
{
if (t == mp_v1) {
return mp_v2;
}
if (t == mp_v2) {
return mp_v1;
}
tl_assert (false);
return 0;
}
bool
TriangleEdge::has_vertex (const Vertex *v) const
{
return mp_v1 == v || mp_v2 == v;
}
Vertex *
TriangleEdge::common_vertex (const TriangleEdge *other) const
{
if (has_vertex (other->v1 ())) {
return (other->v1 ());
}
if (has_vertex (other->v2 ())) {
return (other->v2 ());
}
return 0;
}
std::string
TriangleEdge::to_string (bool with_id) const
{
std::string res = std::string ("(") + mp_v1->to_string (with_id) + ", " + mp_v2->to_string (with_id) + ")";
if (with_id) {
res += tl::sprintf ("[%x]", (size_t)this);
}
return res;
}
double
TriangleEdge::distance (const db::DEdge &e, const db::DPoint &p)
{
double l = db::sprod (p - e.p1 (), e.d ()) / e.d ().sq_length ();
db::DPoint pp;
if (l <= 0.0) {
pp = e.p1 ();
} else if (l >= 1.0) {
pp = e.p2 ();
} else {
pp = e.p1 () + e.d () * l;
}
return (p - pp).length ();
}
bool
TriangleEdge::crosses (const db::DEdge &e, const db::DEdge &other)
{
return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) < 0 &&
other.side_of (e.p1 ()) * other.side_of (e.p2 ()) < 0;
}
bool
TriangleEdge::crosses_including (const db::DEdge &e, const db::DEdge &other)
{
return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) <= 0 &&
other.side_of (e.p1 ()) * other.side_of (e.p2 ()) <= 0;
}
db::DPoint
TriangleEdge::intersection_point (const db::DEdge &e, const db::DEdge &other)
{
return e.intersect_point (other).second;
}
bool
TriangleEdge::point_on (const db::DEdge &edge, const db::DPoint &point)
{
if (edge.side_of (point) != 0) {
return false;
} else {
return db::sprod_sign (point - edge.p1 (), edge.d ()) * db::sprod_sign(point - edge.p2 (), edge.d ()) < 0;
}
}
bool
TriangleEdge::can_flip () const
{
if (! left () || ! right ()) {
return false;
}
const db::Vertex *v1 = left ()->opposite (this);
const db::Vertex *v2 = right ()->opposite (this);
return crosses (db::DEdge (*v1, *v2));
}
bool
TriangleEdge::can_join_via (const Vertex *vertex) const
{
if (! left () || ! right ()) {
return false;
}
tl_assert (has_vertex (vertex));
const db::Vertex *v1 = left ()->opposite (this);
const db::Vertex *v2 = right ()->opposite (this);
return db::DEdge (*v1, *v2).side_of (*vertex) == 0;
}
bool
TriangleEdge::is_outside () const
{
return left () == 0 || right () == 0;
}
bool
TriangleEdge::is_for_outside_triangles () const
{
return (left () && left ()->is_outside ()) || (right () && right ()->is_outside ());
}
bool
TriangleEdge::has_triangle (const Triangle *t) const
{
return t != 0 && (left () == t || right () == t);
}
// -------------------------------------------------------------------------------------
// Triangle implementation
Triangle::Triangle ()
: m_is_outside (false), m_id (0)
{
for (int i = 0; i < 3; ++i) {
mp_v[i] = 0;
mp_e[i] = 0;
}
}
Triangle::Triangle (TriangleEdge *e1, TriangleEdge *e2, TriangleEdge *e3)
: m_is_outside (false), m_id (0)
{
mp_e[0] = e1;
mp_v[0] = e1->v1 ();
mp_v[1] = e1->v2 ();
if (e2->has_vertex (mp_v[1])) {
mp_e[1] = e2;
mp_e[2] = e3;
} else {
mp_e[1] = e3;
mp_e[2] = e2;
}
mp_v[2] = mp_e[1]->other (mp_v[1]);
// enforce clockwise orientation
int s = db::vprod_sign (*mp_v[2] - *mp_v[0], *mp_v[1] - *mp_v[0]);
if (s < 0) {
std::swap (mp_v[2], mp_v[1]);
} else if (s == 0) {
// Triangle is not orientable
tl_assert (false);
}
// establish link to edges
for (int i = 0; i < 3; ++i) {
TriangleEdge *e = mp_e[i];
unsigned int i1 = 0;
for ( ; e->v1 () != mp_v[i1] && i1 < 3; ++i1)
;
unsigned int i2 = 0;
for ( ; e->v2 () != mp_v[i2] && i2 < 3; ++i2)
;
if ((i1 + 1) % 3 == i2) {
e->set_right (this);
} else {
e->set_left (this);
}
}
}
Triangle::~Triangle ()
{
unlink ();
}
void
Triangle::unlink ()
{
for (int i = 0; i != 3; ++i) {
db::TriangleEdge *e = mp_e[i];
if (e->left () == this) {
e->set_left (0);
}
if (e->right () == this) {
e->set_right (0);
}
}
}
std::string
Triangle::to_string (bool with_id) const
{
std::string res = "(";
for (int i = 0; i < 3; ++i) {
if (i > 0) {
res += ", ";
}
if (vertex (i)) {
res += vertex (i)->to_string (with_id);
} else {
res += "(null)";
}
}
res += ")";
return res;
}
double
Triangle::area () const
{
return fabs (db::vprod (mp_e[0]->d (), mp_e[1]->d ())) * 0.5;
}
db::DBox
Triangle::bbox () const
{
db::DBox box;
for (int i = 0; i < 3; ++i) {
box += *mp_v[i];
}
return box;
}
std::pair<db::DPoint, double>
Triangle::circumcircle (bool *ok) const
{
// see https://en.wikipedia.org/wiki/Circumcircle
// we set A=(0,0), so the formulas simplify
if (ok) {
*ok = true;
}
db::DVector b = *mp_v[1] - *mp_v[0];
db::DVector c = *mp_v[2] - *mp_v[0];
double b2 = b.sq_length ();
double c2 = c.sq_length ();
double sx = 0.5 * (b2 * c.y () - c2 * b.y ());
double sy = 0.5 * (b.x () * c2 - c.x() * b2);
double a1 = b.x() * c.y();
double a2 = c.x() * b.y();
double a = a1 - a2;
double a_abs = std::abs (a);
if (a_abs < (std::abs (a1) + std::abs (a2)) * db::epsilon) {
if (ok) {
*ok = false;
return std::make_pair (db::DPoint (), 0.0);
} else {
tl_assert (false);
}
}
double radius = sqrt (sx * sx + sy * sy) / a_abs;
db::DPoint center = *mp_v[0] + db::DVector (sx / a, sy / a);
return std::make_pair (center, radius);
}
Vertex *
Triangle::opposite (const TriangleEdge *edge) const
{
for (int i = 0; i < 3; ++i) {
Vertex *v = mp_v[i];
if (! edge->has_vertex (v)) {
return v;
}
}
tl_assert (false);
}
TriangleEdge *
Triangle::opposite (const Vertex *vertex) const
{
for (int i = 0; i < 3; ++i) {
TriangleEdge *e = mp_e[i];
if (! e->has_vertex (vertex)) {
return e;
}
}
tl_assert (false);
}
TriangleEdge *
Triangle::find_edge_with (const Vertex *v1, const Vertex *v2) const
{
for (int i = 0; i < 3; ++i) {
TriangleEdge *e = mp_e[i];
if (e->has_vertex (v1) && e->has_vertex (v2)) {
return e;
}
}
tl_assert (false);
}
TriangleEdge *
Triangle::common_edge (const Triangle *other) const
{
for (int i = 0; i < 3; ++i) {
TriangleEdge *e = mp_e[i];;
if (e->other (this) == other) {
return e;
}
}
return 0;
}
int
Triangle::contains (const db::DPoint &point) const
{
auto c = *mp_v[2] - *mp_v[0];
auto b = *mp_v[1] - *mp_v[0];
int vps = db::vprod_sign (c, b);
if (vps == 0) {
return db::vprod_sign (point - *mp_v[0], b) == 0 && db::vprod_sign (point - *mp_v[0], c) == 0 ? 0 : -1;
}
int res = 1;
const Vertex *vl = mp_v[2];
for (int i = 0; i < 3; ++i) {
const Vertex *v = mp_v[i];
int n = db::vprod_sign (point - *vl, *v - *vl) * vps;
if (n < 0) {
return -1;
} else if (n == 0) {
res = 0;
}
vl = v;
}
return res;
}
double
Triangle::min_edge_length () const
{
double lmin = mp_e[0]->d ().length ();
for (int i = 1; i < 3; ++i) {
lmin = std::min (lmin, mp_e[i]->d ().length ());
}
return lmin;
}
double
Triangle::b () const
{
double lmin = min_edge_length ();
bool ok = false;
auto cr = circumcircle (&ok);
return ok ? lmin / cr.second : 0.0;
}
bool
Triangle::has_segment () const
{
for (int i = 0; i < 3; ++i) {
if (mp_e[i]->is_segment ()) {
return true;
}
}
return false;
}
unsigned int
Triangle::num_segments () const
{
unsigned int n = 0;
for (int i = 0; i < 3; ++i) {
if (mp_e[i]->is_segment ()) {
++n;
}
}
return n;
}
}

View File

@ -1,579 +0,0 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_dbTriangle
#define HDR_dbTriangle
#include "dbCommon.h"
#include "dbPoint.h"
#include "dbEdge.h"
#include "tlObjectCollection.h"
#include "tlList.h"
#include <vector>
#include <string>
#include <list>
#include <algorithm>
namespace db
{
class Triangle;
class TriangleEdge;
/**
* @brief A class representing a vertex in a Delaunay triangulation graph
*
* The vertex carries information about the connected edges and
* an integer value that can be used in traversal algorithms
* ("level")
*/
class DB_PUBLIC Vertex
: public db::DPoint
{
public:
typedef std::list<TriangleEdge *> edges_type;
typedef edges_type::const_iterator edges_iterator;
typedef edges_type::iterator edges_iterator_non_const;
Vertex ();
Vertex (const DPoint &p);
Vertex (const Vertex &v);
Vertex (db::DCoord x, db::DCoord y);
Vertex &operator= (const Vertex &v);
bool is_outside () const;
std::vector<db::Triangle *> triangles () const;
edges_iterator begin_edges () const { return mp_edges.begin (); }
edges_iterator end_edges () const { return mp_edges.end (); }
size_t num_edges (int max_count = -1) const;
bool has_edge (const TriangleEdge *edge) const;
void set_is_precious (bool f) { m_is_precious = f; }
bool is_precious () const { return m_is_precious; }
std::string to_string (bool with_id = false) const;
/**
* @brief Returns 1 is the point is inside the circle, 0 if on the circle and -1 if outside
* TODO: Move to db::DPoint
*/
static int in_circle (const db::DPoint &point, const db::DPoint &center, double radius);
/**
* @brief Returns 1 is this point is inside the circle, 0 if on the circle and -1 if outside
*/
int in_circle (const db::DPoint &center, double radius) const
{
return in_circle (*this, center, radius);
}
private:
friend class TriangleEdge;
void remove_edge (const edges_iterator_non_const &ec)
{
mp_edges.erase (ec);
}
edges_type mp_edges;
bool m_is_precious;
};
/**
* @brief A class representing an edge in the Delaunay triangulation graph
*/
class DB_PUBLIC TriangleEdge
{
public:
class TriangleIterator
{
public:
typedef Triangle value_type;
typedef Triangle &reference;
typedef Triangle *pointer;
reference operator*() const
{
return *operator-> ();
}
pointer operator->() const
{
return m_index ? mp_edge->right () : mp_edge->left ();
}
bool operator== (const TriangleIterator &other) const
{
return m_index == other.m_index;
}
bool operator!= (const TriangleIterator &other) const
{
return !operator== (other);
}
TriangleIterator &operator++ ()
{
while (++m_index < 2 && operator-> () == 0)
;
return *this;
}
private:
friend class TriangleEdge;
TriangleIterator (const TriangleEdge *edge)
: mp_edge (edge), m_index (0)
{
if (! edge) {
m_index = 2;
} else {
--m_index;
operator++ ();
}
}
const TriangleEdge *mp_edge;
unsigned int m_index;
};
TriangleEdge ();
TriangleEdge (Vertex *v1, Vertex *v2);
Vertex *v1 () const { return mp_v1; }
Vertex *v2 () const { return mp_v2; }
void reverse ()
{
std::swap (mp_v1, mp_v2);
std::swap (mp_left, mp_right);
}
Triangle *left () const { return mp_left; }
Triangle *right () const { return mp_right; }
TriangleIterator begin_triangles () const
{
return TriangleIterator (this);
}
TriangleIterator end_triangles () const
{
return TriangleIterator (0);
}
void set_level (size_t l) { m_level = l; }
size_t level () const { return m_level; }
void set_id (size_t id) { m_id = id; }
size_t id () const { return m_id; }
void set_is_segment (bool is_seg) { m_is_segment = is_seg; }
bool is_segment () const { return m_is_segment; }
std::string to_string (bool with_id = false) const;
/**
* @brief Converts to an db::DEdge
*/
db::DEdge edge () const
{
return db::DEdge (*mp_v1, *mp_v2);
}
/**
* @brief Returns the distance of the given point to the edge
*
* The distance is the minimum distance of the point to one point from the edge.
* TODO: Move to db::DEdge
*/
static double distance (const db::DEdge &e, const db::DPoint &p);
/**
* @brief Returns the distance of the given point to the edge
*
* The distance is the minimum distance of the point to one point from the edge.
*/
double distance (const db::DPoint &p) const
{
return distance (edge (), p);
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
*
* "crosses" is true, if both edges share at least one point which is not an endpoint
* of one of the edges.
* TODO: Move to db::DEdge
*/
static bool crosses (const db::DEdge &e, const db::DEdge &other);
/**
* @brief Returns a value indicating whether this edge crosses the other one
*
* "crosses" is true, if both edges share at least one point which is not an endpoint
* of one of the edges.
*/
bool crosses (const db::DEdge &other) const
{
return crosses (edge (), other);
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
*
* "crosses" is true, if both edges share at least one point which is not an endpoint
* of one of the edges.
*/
bool crosses (const db::TriangleEdge &other) const
{
return crosses (edge (), other.edge ());
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
* "crosses" is true, if both edges share at least one point.
* TODO: Move to db::DEdge
*/
static bool crosses_including (const db::DEdge &e, const db::DEdge &other);
/**
* @brief Returns a value indicating whether this edge crosses the other one
* "crosses" is true, if both edges share at least one point.
*/
bool crosses_including (const db::DEdge &other) const
{
return crosses_including (edge (), other);
}
/**
* @brief Returns a value indicating whether this edge crosses the other one
* "crosses" is true, if both edges share at least one point.
*/
bool crosses_including (const db::TriangleEdge &other) const
{
return crosses_including (edge (), other.edge ());
}
/**
* @brief Gets the intersection point
* TODO: Move to db::DEdge
*/
static db::DPoint intersection_point (const db::DEdge &e, const DEdge &other);
/**
* @brief Gets the intersection point
*/
db::DPoint intersection_point (const db::DEdge &other) const
{
return intersection_point (edge (), other);
}
/**
* @brief Gets the intersection point
*/
db::DPoint intersection_point (const TriangleEdge &other) const
{
return intersection_point (edge (), other.edge ());
}
/**
* @brief Returns a value indicating whether the point is on the edge
* TODO: Move to db::DEdge
*/
static bool point_on (const db::DEdge &edge, const db::DPoint &point);
/**
* @brief Returns a value indicating whether the point is on the edge
*/
bool point_on (const db::DPoint &point) const
{
return point_on (edge (), point);
}
/**
* @brief Gets the side the point is on
*
* -1 is for "left", 0 is "on" and +1 is "right"
* TODO: correct to same definition as db::Edge (negative)
*/
static int side_of (const db::DEdge &e, const db::DPoint &point)
{
return -e.side_of (point);
}
/**
* @brief Gets the side the point is on
*
* -1 is for "left", 0 is "on" and +1 is "right"
* TODO: correct to same definition as db::Edge (negative)
*/
int side_of (const db::DPoint &p) const
{
return -edge ().side_of (p);
}
/**
* @brief Gets the distance vector
*/
db::DVector d () const
{
return *mp_v2 - *mp_v1;
}
/**
* @brief Gets the other triangle for the given one
*/
Triangle *other (const Triangle *) const;
/**
* @brief Gets the other vertex for the given one
*/
Vertex *other (const Vertex *) const;
/**
* @brief Gets a value indicating whether the edge has the given vertex
*/
bool has_vertex (const Vertex *) const;
/**
* @brief Gets the common vertex of the other edge and this edge or null if there is no common vertex
*/
Vertex *common_vertex (const TriangleEdge *other) const;
/**
* @brief Returns a value indicating whether this edge can be flipped
*/
bool can_flip () const;
/**
* @brief Returns a value indicating whether the edge separates two triangles that can be joined into one (via the given vertex)
*/
bool can_join_via (const Vertex *vertex) const;
/**
* @brief Returns a value indicating whether this edge is an outside edge (no other triangles)
*/
bool is_outside () const;
/**
* @brief Returns a value indicating whether this edge belongs to outside triangles
*/
bool is_for_outside_triangles () const;
/**
* @brief Returns a value indicating whether t is attached to this edge
*/
bool has_triangle (const Triangle *t) const;
protected:
void unlink ();
void link ();
private:
friend class Triangle;
friend class Triangles;
Vertex *mp_v1, *mp_v2;
Triangle *mp_left, *mp_right;
Vertex::edges_iterator_non_const m_ec_v1, m_ec_v2;
size_t m_level;
size_t m_id;
bool m_is_segment;
void set_left (Triangle *t);
void set_right (Triangle *t);
};
/**
* @brief A compare function that compares triangles by ID
*
* The ID acts as a more predicable unique ID for the object in sets and maps.
*/
struct TriangleEdgeLessFunc
{
bool operator () (TriangleEdge *a, TriangleEdge *b) const
{
return a->id () < b->id ();
}
};
/**
* @brief A class representing a triangle
*/
class DB_PUBLIC Triangle
: public tl::list_node<Triangle>, public tl::Object
{
public:
Triangle ();
Triangle (TriangleEdge *e1, TriangleEdge *e2, TriangleEdge *e3);
~Triangle ();
void unlink ();
void set_id (size_t id) { m_id = id; }
size_t id () const { return m_id; }
bool is_outside () const { return m_is_outside; }
void set_outside (bool o) { m_is_outside = o; }
std::string to_string (bool with_id = false) const;
/**
* @brief Gets the nth vertex (n wraps around and can be negative)
* The vertexes are oriented clockwise.
*/
inline Vertex *vertex (int n) const
{
if (n >= 0 && n < 3) {
return mp_v[n];
} else {
return mp_v[(n + 3) % 3];
}
}
/**
* @brief Gets the nth edge (n wraps around and can be negative)
*/
inline TriangleEdge *edge (int n) const
{
if (n >= 0 && n < 3) {
return mp_e[n];
} else {
return mp_e[(n + 3) % 3];
}
}
/**
* @brief Gets the area
*/
double area () const;
/**
* @brief Returns the bounding box of the triangle
*/
db::DBox bbox () const;
/**
* @brief Gets the center point and radius of the circumcircle
* If ok is non-null, it will receive a boolean value indicating whether the circumcircle is valid.
* An invalid circumcircle is an indicator for a degenerated triangle with area 0 (or close to).
*/
std::pair<db::DPoint, double> circumcircle (bool *ok = 0) const;
/**
* @brief Gets the vertex opposite of the given edge
*/
Vertex *opposite (const TriangleEdge *edge) const;
/**
* @brief Gets the edge opposite of the given vertex
*/
TriangleEdge *opposite (const Vertex *vertex) const;
/**
* @brief Gets the edge with the given vertexes
*/
TriangleEdge *find_edge_with (const Vertex *v1, const Vertex *v2) const;
/**
* @brief Finds the common edge for both triangles
*/
TriangleEdge *common_edge (const Triangle *other) const;
/**
* @brief Returns a value indicating whether the point is inside (1), on the triangle (0) or outside (-1)
*/
int contains (const db::DPoint &point) const;
/**
* @brief Gets a value indicating whether the triangle has the given vertex
*/
inline bool has_vertex (const db::Vertex *v) const
{
return mp_v[0] == v || mp_v[1] == v || mp_v[2] == v;
}
/**
* @brief Gets a value indicating whether the triangle has the given edge
*/
inline bool has_edge (const db::TriangleEdge *e) const
{
return mp_e[0] == e || mp_e[1] == e || mp_e[2] == e;
}
/**
* @brief Returns the minimum edge length
*/
double min_edge_length () const;
/**
* @brief Returns the min edge length to circumcircle radius ratio
*/
double b () const;
/**
* @brief Returns a value indicating whether the triangle borders to a segment
*/
bool has_segment () const;
/**
* @brief Returns the number of segments the triangle borders to
*/
unsigned int num_segments () const;
private:
bool m_is_outside;
TriangleEdge *mp_e[3];
db::Vertex *mp_v[3];
size_t m_id;
// no copying
Triangle &operator= (const Triangle &);
Triangle (const Triangle &);
};
/**
* @brief A compare function that compares triangles by ID
*
* The ID acts as a more predicable unique ID for the object in sets and maps.
*/
struct TriangleLessFunc
{
bool operator () (Triangle *a, Triangle *b) const
{
return a->id () < b->id ();
}
};
}
#endif

View File

@ -1,357 +0,0 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_dbTriangles
#define HDR_dbTriangles
#include "dbCommon.h"
#include "dbTriangle.h"
#include "dbBox.h"
#include "dbRegion.h"
#include "tlObjectCollection.h"
#include "tlStableVector.h"
#include <limits>
#include <list>
#include <vector>
#include <algorithm>
namespace db
{
class Layout;
class DB_PUBLIC Triangles
{
public:
struct TriangulateParameters
{
TriangulateParameters ()
: min_b (1.0),
min_length (0.0),
max_area (0.0),
max_area_border (0.0),
max_iterations (std::numeric_limits<size_t>::max ()),
base_verbosity (30),
mark_triangles (false)
{ }
/**
* @brief Min. readius-to-shortest edge ratio
*/
double min_b;
/**
* @brief Min. edge length
*
* This parameter does not provide a guarantee about a minimume edge length, but
* helps avoiding ever-reducing triangle splits in acute corners of the input polygon.
* Splitting of edges stops when the edge is less than the min length.
*/
double min_length;
/**
* @brief Max area or zero for "no constraint"
*/
double max_area;
/**
* @brief Max area for border triangles or zero for "use max_area"
*/
double max_area_border;
/**
* @brief Max number of iterations
*/
size_t max_iterations;
/**
* @brief The verbosity level above which triangulation reports details
*/
int base_verbosity;
/**
* @brief If true, final triangles are marked using the "id" integer as a bit field
*
* This provides information about the result quality.
*
* Bit 0: skinny triangle
* Bit 1: bad-quality (skinny or area too large)
* Bit 2: non-Delaunay (in the strict sense)
*/
bool mark_triangles;
};
typedef tl::list<db::Triangle> triangles_type;
typedef triangles_type::const_iterator triangle_iterator;
Triangles ();
~Triangles ();
/**
* @brief Initializes the triangle collection with a box
* Two triangles will be created.
*/
void init_box (const db::DBox &box);
/**
* @brief Returns a string representation of the triangle graph.
*/
std::string to_string ();
/**
* @brief Returns the bounding box of the triangle graph.
*/
db::DBox bbox () const;
/**
* @brief Iterates the triangles in the graph (begin iterator)
*/
triangle_iterator begin () const { return mp_triangles.begin (); }
/**
* @brief Iterates the triangles in the graph (end iterator)
*/
triangle_iterator end () const { return mp_triangles.end (); }
/**
* @brief Returns the number of triangles in the graph
*/
size_t num_triangles () const { return mp_triangles.size (); }
/**
* @brief Clears the triangle set
*/
void clear ();
/**
* @brief Creates a refined Delaunay triangulation for the given region
*
* The database unit should be chosen in a way that target area values are "in the order of 1".
* For inputs featuring acute angles (angles < ~25 degree), the parameters should defined a min
* edge length ("min_length").
* "min_length" should be at least 1e-4. If a min edge length is given, the max area constaints
* may not be satisfied.
*
* Edges in the input should not be shorter than 1e-4.
*/
void triangulate (const db::Region &region, const TriangulateParameters &parameters, double dbu = 1.0);
// more versions
void triangulate (const db::Region &region, const TriangulateParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
void triangulate (const db::Polygon &poly, const TriangulateParameters &parameters, double dbu = 1.0);
void triangulate (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const TriangulateParameters &parameters, double dbu = 1.0);
void triangulate (const db::Polygon &poly, const TriangulateParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
void triangulate (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const TriangulateParameters &parameters, const db::CplxTrans &trans = db::CplxTrans ());
/**
* @brief Triangulates a floating-point polygon
*/
void triangulate (const db::DPolygon &poly, const TriangulateParameters &parameters, const db::DCplxTrans &trans = db::DCplxTrans ());
void triangulate (const db::DPolygon &poly, const std::vector<db::DPoint> &vertexes, const TriangulateParameters &parameters, const db::DCplxTrans &trans = db::DCplxTrans ());
/**
* @brief Statistics: number of flips (fixing)
*/
size_t flips () const
{
return m_flips;
}
/**
* @brief Statistics: number of hops (searching)
*/
size_t hops () const
{
return m_hops;
}
protected:
/**
* @brief Checks the triangle graph for consistency
* This method is for testing purposes mainly.
*/
bool check (bool check_delaunay = true) const;
/**
* @brief Dumps the triangle graph to a GDS file at the given path
* This method is for testing purposes mainly.
*
* "decompose_id" will map triangles to layer 20, 21 and 22.
* according to bit 0, 1 and 2 of the ID (useful with the 'mark_triangles'
* flat in TriangulateParameters).
*/
void dump (const std::string &path, bool decompose_by_id = false) const;
/**
* @brief Creates a new layout object representing the triangle graph
* This method is for testing purposes mainly.
*/
db::Layout *to_layout (bool decompose_by_id = false) const;
/**
* @brief Finds the points within (not "on") a circle of radius "radius" around the given vertex.
*/
std::vector<db::Vertex *> find_points_around (Vertex *vertex, double radius);
/**
* @brief Inserts a new vertex as the given point
*
* If "new_triangles" is not null, it will receive the list of new triangles created during
* the remove step.
*/
db::Vertex *insert_point (const db::DPoint &point, std::list<tl::weak_ptr<db::Triangle> > *new_triangles = 0);
/**
* @brief Inserts a new vertex as the given point
*
* If "new_triangles" is not null, it will receive the list of new triangles created during
* the remove step.
*/
db::Vertex *insert_point (db::DCoord x, db::DCoord y, std::list<tl::weak_ptr<db::Triangle> > *new_triangles = 0);
/**
* @brief Removes the given vertex
*
* If "new_triangles" is not null, it will receive the list of new triangles created during
* the remove step.
*/
void remove (db::Vertex *vertex, std::list<tl::weak_ptr<db::Triangle> > *new_triangles = 0);
/**
* @brief Flips the given edge
*/
std::pair<std::pair<db::Triangle *, db::Triangle *>, db::TriangleEdge *> flip (TriangleEdge *edge);
/**
* @brief Finds all edges that cross the given one for a convex triangulation
*
* Requirements:
* * self must be a convex triangulation
* * edge must not contain another vertex from the triangulation except p1 and p2
*/
std::vector<db::TriangleEdge *> search_edges_crossing (db::Vertex *from, db::Vertex *to);
/**
* @brief Finds the edge for two given points
*/
db::TriangleEdge *find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2);
/**
* @brief Finds the vertex for a point
*/
db::Vertex *find_vertex_for_point (const db::DPoint &pt);
/**
* @brief Ensures all points between from an to are connected by edges and makes these segments
*/
std::vector<db::TriangleEdge *> ensure_edge (db::Vertex *from, db::Vertex *to);
/**
* @brief Given a set of contours with edges, mark outer triangles
*
* The edges must be made from existing vertexes. Edge orientation is
* clockwise.
*
* This will also mark triangles as outside ones.
*/
void constrain (const std::vector<std::vector<Vertex *> > &contours);
/**
* @brief Removes the outside triangles.
*/
void remove_outside_triangles ();
/**
* @brief Creates a constrained Delaunay triangulation from the given Region
*/
void create_constrained_delaunay (const db::Region &region, const db::CplxTrans &trans = db::CplxTrans ());
/**
* @brief Creates a constrained Delaunay triangulation from the given Polygon
*/
void create_constrained_delaunay (const db::Polygon &poly, const std::vector<db::Point> &vertexes, const db::CplxTrans &trans = db::CplxTrans ());
/**
* @brief Creates a constrained Delaunay triangulation from the given DPolygon
*/
void create_constrained_delaunay (const db::DPolygon &poly, const std::vector<DPoint> &vertexes, const DCplxTrans &trans);
/**
* @brief Returns a value indicating whether the edge is "illegal" (violates the Delaunay criterion)
*/
static bool is_illegal_edge (db::TriangleEdge *edge);
// NOTE: these functions are SLOW and intended to test purposes only
std::vector<db::Vertex *> find_touching (const db::DBox &box) const;
std::vector<db::Vertex *> find_inside_circle (const db::DPoint &center, double radius) const;
private:
struct TriangleBoxConvert
{
typedef db::DBox box_type;
box_type operator() (db::Triangle *t) const
{
return t ? t->bbox () : box_type ();
}
};
tl::list<db::Triangle> mp_triangles;
tl::stable_vector<db::TriangleEdge> m_edges_heap;
std::vector<db::TriangleEdge *> m_returned_edges;
tl::stable_vector<db::Vertex> m_vertex_heap;
bool m_is_constrained;
size_t m_level;
size_t m_id;
size_t m_flips, m_hops;
db::Vertex *create_vertex (double x, double y);
db::Vertex *create_vertex (const db::DPoint &pt);
db::TriangleEdge *create_edge (db::Vertex *v1, db::Vertex *v2);
db::Triangle *create_triangle (db::TriangleEdge *e1, db::TriangleEdge *e2, db::TriangleEdge *e3);
void remove_triangle (db::Triangle *tri);
void remove_outside_vertex (db::Vertex *vertex, std::list<tl::weak_ptr<db::Triangle> > *new_triangles = 0);
void remove_inside_vertex (db::Vertex *vertex, std::list<tl::weak_ptr<db::Triangle> > *new_triangles_out = 0);
std::vector<db::Triangle *> fill_concave_corners (const std::vector<TriangleEdge *> &edges);
void fix_triangles (const std::vector<db::Triangle *> &tris, const std::vector<db::TriangleEdge *> &fixed_edges, std::list<tl::weak_ptr<db::Triangle> > *new_triangles);
std::vector<db::Triangle *> find_triangle_for_point (const db::DPoint &point);
db::TriangleEdge *find_closest_edge (const db::DPoint &p, db::Vertex *vstart = 0, bool inside_only = false);
db::Vertex *insert (db::Vertex *vertex, std::list<tl::weak_ptr<db::Triangle> > *new_triangles = 0);
void split_triangle (db::Triangle *t, db::Vertex *vertex, std::list<tl::weak_ptr<db::Triangle> > *new_triangles_out);
void split_triangles_on_edge (db::Vertex *vertex, db::TriangleEdge *split_edge, std::list<tl::weak_ptr<db::Triangle> > *new_triangles_out);
void add_more_triangles (std::vector<Triangle *> &new_triangles,
db::TriangleEdge *incoming_edge,
db::Vertex *from_vertex, db::Vertex *to_vertex,
db::TriangleEdge *conn_edge);
void insert_new_vertex(db::Vertex *vertex, std::list<tl::weak_ptr<Triangle> > *new_triangles_out);
std::vector<db::TriangleEdge *> ensure_edge_inner (db::Vertex *from, db::Vertex *to);
void join_edges (std::vector<TriangleEdge *> &edges);
void refine (const TriangulateParameters &param);
template<class Poly, class Trans> void make_contours (const Poly &poly, const Trans &trans, std::vector<std::vector<db::Vertex *> > &contours);
};
}
#endif

View File

@ -1033,7 +1033,7 @@ Class<db::LayoutToNetlist> decl_dbLayoutToNetlist ("db", "LayoutToNetlist",
"'lmap' can also be left nil, in which case, a layer mapping will be provided based on the layer info attributes of "
"the layers (see \\layer_info).\n"
"\n"
"'cmap' specifies the cell mapping. Use \\create_cell_mapping or \\const_create_cell_mapping to "
"'cmap' specifies the cell mapping. Use \\cell_mapping_into or \\const_cell_mapping_into to "
"define the target cells in the target layout and to derive a cell mapping.\n"
"\n"
"The method has three net annotation modes:\n"

View File

@ -28,45 +28,77 @@
#include "dbPolygonTools.h"
#include "dbPolygonGenerators.h"
#include "dbHash.h"
#include "dbTriangles.h"
#include "dbPLCTriangulation.h"
#include "dbPLCConvexDecomposition.h"
namespace gsi
{
const std::string hm_docstring =
"The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the "
"triangles into convex polygons.\n"
"\n"
"The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'.\n"
"\n"
"If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming "
"a concave corner. If false, only diagonals (edges connecting original vertexes) are used.\n"
"\n"
"If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, "
"the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine "
"into longer edges when processing the polygon further. When such a recombination happens, the edges no "
"longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting "
"polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines."
"\n"
"'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \\delaunay).\n"
;
const std::string delaunay_docstring =
"Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles "
"larger than this area will be split. In addition 'skinny' triangles will be resolved where "
"possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). "
"A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree "
"and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to "
"a minimum angle of abouth 37 degree.\n"
"\n"
"The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n"
"\n"
"Picking a value of 0.0 for max_area and min_b will "
"make the implementation skip the refinement step. In that case, the results are identical to "
"the standard constrained Delaunay triangulation.\n"
;
const std::string dbu_docstring =
"The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions "
"are \"in the order of 1\" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable "
"for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.\n"
;
template <class T>
static db::Region region_from_triangles (const db::Triangles &tri, const T &trans)
static db::Region region_from_graph (const db::plc::Graph &plc, const T &trans)
{
db::Region result;
result.set_merged_semantics (false);
db::Point pts [3];
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (int i = 0; i < 3; ++i) {
pts [i] = trans * *t->vertex (i);
}
db::SimplePolygon poly;
poly.assign_hull (pts + 0, pts + 3);
result.insert (poly);
for (auto t = plc.begin (); t != plc.end (); ++t) {
db::DPolygon dp = t->polygon ();
db::SimplePolygon sp;
sp.assign_hull (dp.hull ().begin (), dp.hull ().end (), trans, false);
result.insert (sp);
}
return result;
}
template <class P, class T>
static std::vector<P> polygons_from_triangles (const db::Triangles &tri, const T &trans)
static std::vector<P> polygons_from_graph (const db::plc::Graph &plc, const T &trans)
{
std::vector<P> result;
result.reserve (tri.num_triangles ());
result.reserve (plc.num_polygons ());
typename P::point_type pts [3];
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (int i = 0; i < 3; ++i) {
pts [i] = trans * *t->vertex (i);
}
P poly;
poly.assign_hull (pts + 0, pts + 3);
result.push_back (poly);
for (auto t = plc.begin (); t != plc.end (); ++t) {
db::DPolygon dp = t->polygon ();
result.push_back (P ());
result.back ().assign_hull (dp.hull ().begin (), dp.hull ().end (), trans, false);
}
return result;
@ -87,63 +119,103 @@ static db::polygon<C> to_polygon (const db::polygon<C> &p)
}
template <class P>
static db::Region triangulate_ipolygon (const P *p, double max_area = 0.0, double min_b = 0.0, double dbu = 0.001)
static db::Region hm_decompose_ipolygon (const P *p, bool with_segments, bool split_edges, double max_area, double min_b, double dbu)
{
db::Triangles tris;
db::Triangles::TriangulateParameters param;
param.min_b = min_b;
param.max_area = max_area * dbu * dbu;
db::plc::Graph plc;
db::plc::ConvexDecomposition decomp (&plc);
db::plc::ConvexDecompositionParameters param;
param.with_segments = with_segments;
param.split_edges = split_edges;
param.tri_param.max_area = max_area;
param.tri_param.min_b = min_b;
db::CplxTrans trans = db::CplxTrans (dbu) * db::ICplxTrans (db::Trans (db::Point () - p->box ().center ()));
tris.triangulate (to_polygon (*p), param, trans);
decomp.decompose (to_polygon (*p), param, trans);
return region_from_triangles (tris, trans.inverted ());
return region_from_graph (plc, trans.inverted ());
}
template <class P>
static db::Region triangulate_ipolygon_v (const P *p, const std::vector<db::Point> &vertexes, double max_area = 0.0, double min_b = 0.0, double dbu = 0.001)
static std::vector<P> hm_decompose_dpolygon (const P *p, bool with_segments, bool split_edges, double max_area, double min_b)
{
db::Triangles tris;
db::Triangles::TriangulateParameters param;
db::plc::Graph plc;
db::plc::ConvexDecomposition decomp (&plc);
db::plc::ConvexDecompositionParameters param;
param.with_segments = with_segments;
param.split_edges = split_edges;
param.tri_param.max_area = max_area;
param.tri_param.min_b = min_b;
db::DCplxTrans trans = db::DCplxTrans (db::DTrans (db::DPoint () - p->box ().center ()));
decomp.decompose (to_polygon (*p), param, trans);
return polygons_from_graph<P, db::DCplxTrans> (plc, trans.inverted ());
}
template <class P>
static db::Region triangulate_ipolygon (const P *p, double max_area, double min_b, double dbu)
{
db::plc::Graph tris;
db::plc::Triangulation triangulation (&tris);
db::plc::TriangulationParameters param;
param.min_b = min_b;
param.max_area = max_area * dbu * dbu;
db::CplxTrans trans = db::CplxTrans (dbu) * db::ICplxTrans (db::Trans (db::Point () - p->box ().center ()));
tris.triangulate (to_polygon (*p), vertexes, param, trans);
triangulation.triangulate (to_polygon (*p), param, trans);
return region_from_triangles (tris, trans.inverted ());
return region_from_graph (tris, trans.inverted ());
}
template <class P>
static db::Region triangulate_ipolygon_v (const P *p, const std::vector<db::Point> &vertexes, double max_area, double min_b, double dbu)
{
db::plc::Graph tris;
db::plc::Triangulation triangulation (&tris);
db::plc::TriangulationParameters param;
param.min_b = min_b;
param.max_area = max_area * dbu * dbu;
db::CplxTrans trans = db::CplxTrans (dbu) * db::ICplxTrans (db::Trans (db::Point () - p->box ().center ()));
triangulation.triangulate (to_polygon (*p), vertexes, param, trans);
return region_from_graph (tris, trans.inverted ());
}
template <class P>
static std::vector<P> triangulate_dpolygon (const P *p, double max_area = 0.0, double min_b = 0.0)
{
db::Triangles tris;
db::Triangles::TriangulateParameters param;
db::plc::Graph tris;
db::plc::Triangulation triangulation (&tris);
db::plc::TriangulationParameters param;
param.min_b = min_b;
param.max_area = max_area;
db::DCplxTrans trans = db::DCplxTrans (db::DTrans (db::DPoint () - p->box ().center ()));
tris.triangulate (to_polygon (*p), param, trans);
triangulation.triangulate (to_polygon (*p), param, trans);
return polygons_from_triangles<P, db::DCplxTrans> (tris, trans.inverted ());
return polygons_from_graph<P, db::DCplxTrans> (tris, trans.inverted ());
}
template <class P>
static std::vector<P> triangulate_dpolygon_v (const P *p, const std::vector<db::DPoint> &vertexes, double max_area = 0.0, double min_b = 0.0)
{
db::Triangles tris;
db::Triangles::TriangulateParameters param;
db::plc::Graph tris;
db::plc::Triangulation triangulation (&tris);
db::plc::TriangulationParameters param;
param.min_b = min_b;
param.max_area = max_area;
db::DCplxTrans trans = db::DCplxTrans (db::DTrans (db::DPoint () - p->box ().center ()));
tris.triangulate (to_polygon (*p), vertexes, param, trans);
triangulation.triangulate (to_polygon (*p), vertexes, param, trans);
return polygons_from_triangles<P, db::DCplxTrans> (tris, trans.inverted ());
return polygons_from_graph<P, db::DCplxTrans> (tris, trans.inverted ());
}
template <class C>
@ -880,36 +952,35 @@ Class<db::SimplePolygon> decl_SimplePolygon ("db", "SimplePolygon",
"\n"
"This method has been introduced in version 0.18.\n"
) +
method_ext ("hm_decomposition", &hm_decompose_ipolygon<db::SimplePolygon>,
gsi::arg ("with_segments", true), gsi::arg ("split_edges", false),
gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),
gsi::arg ("dbu", 0.001),
"@brief Performs a Hertel-Mehlhorn convex decomposition.\n"
"\n"
"@return A \\Region holding the polygons of the decomposition.\n"
"The resulting region is in 'no merged semantics' mode, "
"to avoid re-merging of the polygons during following operations.\n"
"\n" + hm_docstring + "\n" + dbu_docstring + "\n"
"This method has been introduced in version 0.30.1."
) +
method_ext ("delaunay", &triangulate_ipolygon<db::SimplePolygon>, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001),
"@brief Performs a Delaunay triangulation of the polygon.\n"
"\n"
"@return A \\Region holding the triangles of the refined, constrained Delaunay triangulation.\n"
"\n"
"Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles "
"larger than this area will be split. In addition 'skinny' triangles will be resolved where "
"possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). "
"A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree "
"and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to "
"a minimum angle of abouth 37 degree.\n"
"\n"
"The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n"
"\n"
"The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will "
"make the implementation skip the refinement step. In that case, the results are identical to "
"the standard constrained Delaunay triangulation.\n"
"\n"
"The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions "
"are \"in the order of 1\" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable "
"for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.\n"
"\n"
"This method has been introduced in version 0.30."
"\n" + delaunay_docstring + "\n"
"The area value is given in terms of DBU units.\n"
"\n" + dbu_docstring + "\n"
"This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, "
"to avoid re-merging of the triangles during following operations."
) +
method_ext ("delaunay", &triangulate_ipolygon_v<db::SimplePolygon>, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001),
"@brief Performs a Delaunay triangulation of the polygon.\n"
"\n"
"This variant of the triangulation function accepts an array of additional vertexes for the triangulation.\n"
"\n"
"This method has been introduced in version 0.30."
"This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, "
"to avoid re-merging of the triangles during following operations."
) +
simple_polygon_defs<db::SimplePolygon>::methods (),
"@brief A simple polygon class\n"
@ -1006,24 +1077,20 @@ Class<db::DSimplePolygon> decl_DSimplePolygon ("db", "DSimplePolygon",
"\n"
"This method has been introduced in version 0.25.\n"
) +
method_ext ("hm_decomposition", &hm_decompose_dpolygon<db::DSimplePolygon>,
gsi::arg ("with_segments", true), gsi::arg ("split_edges", false),
gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),
"@brief Performs a Hertel-Mehlhorn convex decomposition.\n"
"\n"
"@return An array holding the polygons of the decomposition.\n"
"\n" + hm_docstring + "\n"
"This method has been introduced in version 0.30.1."
) +
method_ext ("delaunay", &triangulate_dpolygon<db::DSimplePolygon>, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),
"@brief Performs a Delaunay triangulation of the polygon.\n"
"\n"
"@return An array of triangular polygons of the refined, constrained Delaunay triangulation.\n"
"\n"
"Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles "
"larger than this area will be split. In addition 'skinny' triangles will be resolved where "
"possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). "
"A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree "
"and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to "
"a minimum angle of abouth 37 degree.\n"
"\n"
"The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n"
"\n"
"Picking a value of 0.0 for max area and min b will "
"make the implementation skip the refinement step. In that case, the results are identical to "
"the standard constrained Delaunay triangulation.\n"
"\n"
"\n" + delaunay_docstring + "\n"
"This method has been introduced in version 0.30."
) +
method_ext ("delaunay", &triangulate_dpolygon_v<db::DSimplePolygon>, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),
@ -2207,36 +2274,36 @@ Class<db::Polygon> decl_Polygon ("db", "Polygon",
"\n"
"This method was introduced in version 0.18.\n"
) +
method_ext ("hm_decomposition", &hm_decompose_ipolygon<db::Polygon>,
gsi::arg ("with_segments", true), gsi::arg ("split_edges", false),
gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),
gsi::arg ("dbu", 0.001),
"@brief Performs a Hertel-Mehlhorn convex decomposition.\n"
"\n"
"@return A \\Region holding the polygons of the decomposition.\n"
"\n"
"The resulting region is in 'no merged semantics' mode, "
"to avoid re-merging of the polygons during following operations.\n"
"\n" + hm_docstring + "\n" + dbu_docstring + "\n"
"This method has been introduced in version 0.30.1."
) +
method_ext ("delaunay", &triangulate_ipolygon<db::Polygon>, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001),
"@brief Performs a Delaunay triangulation of the polygon.\n"
"\n"
"@return A \\Region holding the triangles of the refined, constrained Delaunay triangulation.\n"
"\n"
"Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles "
"larger than this area will be split. In addition 'skinny' triangles will be resolved where "
"possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). "
"A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree "
"and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to "
"a minimum angle of abouth 37 degree.\n"
"\n"
"The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n"
"\n"
"The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will "
"make the implementation skip the refinement step. In that case, the results are identical to "
"the standard constrained Delaunay triangulation.\n"
"\n"
"The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions "
"are \"in the order of 1\" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable "
"for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.\n"
"\n"
"This method has been introduced in version 0.30."
"\n" + delaunay_docstring + "\n"
"The area value is given in terms of DBU units.\n"
"\n" + dbu_docstring + "\n"
"This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, "
"to avoid re-merging of the triangles during following operations."
) +
method_ext ("delaunay", &triangulate_ipolygon_v<db::Polygon>, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001),
"@brief Performs a Delaunay triangulation of the polygon.\n"
"\n"
"This variant of the triangulation function accepts an array of additional vertexes for the triangulation.\n"
"\n"
"This method has been introduced in version 0.30."
"This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, "
"to avoid re-merging of the triangles during following operations."
) +
polygon_defs<db::Polygon>::methods (),
"@brief A polygon class\n"
@ -2360,24 +2427,20 @@ Class<db::DPolygon> decl_DPolygon ("db", "DPolygon",
"\n"
"This method has been introduced in version 0.25.\n"
) +
method_ext ("hm_decomposition", &hm_decompose_dpolygon<db::DPolygon>,
gsi::arg ("with_segments", true), gsi::arg ("split_edges", false),
gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),
"@brief Performs a Hertel-Mehlhorn convex decomposition.\n"
"\n"
"@return An array holding the polygons of the decomposition.\n"
"\n" + hm_docstring + "\n"
"This method has been introduced in version 0.30.1."
) +
method_ext ("delaunay", &triangulate_dpolygon<db::DPolygon>, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),
"@brief Performs a Delaunay triangulation of the polygon.\n"
"\n"
"@return An array of triangular polygons of the refined, constrained Delaunay triangulation.\n"
"\n"
"Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles "
"larger than this area will be split. In addition 'skinny' triangles will be resolved where "
"possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). "
"A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree "
"and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to "
"a minimum angle of abouth 37 degree.\n"
"\n"
"The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n"
"\n"
"Picking a value of 0.0 for max area and min b will "
"make the implementation skip the refinement step. In that case, the results are identical to "
"the standard constrained Delaunay triangulation.\n"
"\n"
"\n" + delaunay_docstring + "\n"
"This method has been introduced in version 0.30."
) +
method_ext ("delaunay", &triangulate_dpolygon_v<db::DPolygon>, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0),

View File

@ -521,7 +521,7 @@ struct trans_defs
method ("disp", (const vector_type &(C::*) () const) &C::disp,
"@brief Gets to the displacement vector\n"
"\n"
"Staring with version 0.25 the displacement type is a vector."
"Starting with version 0.25 the displacement type is a vector."
) +
method ("rot", &C::rot,
"@brief Gets the angle/mirror code\n"
@ -553,7 +553,7 @@ struct trans_defs
"@param u The new displacement\n"
"\n"
"This method was introduced in version 0.20.\n"
"Staring with version 0.25 the displacement type is a vector."
"Starting with version 0.25 the displacement type is a vector."
) +
method_ext ("mirror=", &set_mirror, arg ("m"),
"@brief Sets the mirror flag\n"

View File

@ -0,0 +1,189 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbPLCConvexDecomposition.h"
#include "dbWriter.h"
#include "dbRegionProcessors.h"
#include "dbTestSupport.h"
#include "tlUnitTest.h"
#include "tlStream.h"
#include "tlFileUtils.h"
#include <set>
#include <vector>
#include <cstdlib>
#include <cmath>
class TestableConvexDecomposition
: public db::plc::ConvexDecomposition
{
public:
using db::plc::ConvexDecomposition::ConvexDecomposition;
};
TEST(basic)
{
db::plc::Graph plc;
TestableConvexDecomposition decomp (&plc);
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 100),
db::Point (1000, 100),
db::Point (1000, 500),
db::Point (1100, 500),
db::Point (1100, 100),
db::Point (2100, 100),
db::Point (2100, 0)
};
db::Point contour2[] = {
db::Point (4000, 0),
db::Point (4000, 100),
db::Point (5000, 100),
db::Point (5000, 500),
db::Point (5100, 500),
db::Point (5100, 100),
db::Point (6100, 100),
db::Point (6100, -1000),
db::Point (4150, -1000),
db::Point (4150, 0)
};
db::Region region;
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
region.insert (poly);
poly.clear ();
poly.assign_hull (contour2 + 0, contour2 + sizeof (contour2) / sizeof (contour2[0]));
region.insert (poly);
double dbu = 0.001;
db::plc::ConvexDecompositionParameters param;
decomp.decompose (region, param, dbu);
std::unique_ptr<db::Layout> ly (plc.to_layout ());
db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au1.gds");
param.with_segments = true;
param.split_edges = false;
decomp.decompose (region, param, dbu);
ly.reset (plc.to_layout ());
db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au2.gds");
param.with_segments = false;
param.split_edges = true;
decomp.decompose (region, param, dbu);
ly.reset (plc.to_layout ());
db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au3.gds");
param.with_segments = true;
param.split_edges = true;
decomp.decompose (region, param, dbu);
ly.reset (plc.to_layout ());
db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au4.gds");
}
TEST(internal_vertex)
{
db::plc::Graph plc;
TestableConvexDecomposition decomp (&plc);
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 100),
db::Point (1000, 100),
db::Point (1000, 0)
};
std::vector<db::Point> vertexes;
vertexes.push_back (db::Point (0, 50)); // on edge
vertexes.push_back (db::Point (200, 70));
vertexes.push_back (db::Point (0, 0)); // on vertex
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
db::plc::ConvexDecompositionParameters param;
decomp.decompose (poly, vertexes, param, dbu);
EXPECT_EQ (plc.begin () == plc.end (), false);
if (plc.begin () == plc.end ()) {
return;
}
auto p = plc.begin ();
EXPECT_EQ (p->polygon ().to_string (), "(0,0;0,0.05;0,0.1;1,0.1;1,0)");
std::vector<std::string> ip;
for (size_t i = 0; i < p->internal_vertexes (); ++i) {
ip.push_back (p->internal_vertex (i)->to_string () + "#" + tl::join (p->internal_vertex (i)->ids ().begin (), p->internal_vertex (i)->ids ().end (), ","));
}
std::sort (ip.begin (), ip.end ());
EXPECT_EQ (tl::join (ip, "/"), "(0, 0)#2/(0, 0.05)#0/(0.2, 0.07)#1");
EXPECT_EQ (++p == plc.end (), true);
}
TEST(problematic_polygon)
{
db::Point contour[] = {
db::Point (14590, 990),
db::Point (6100, 990),
db::Point (7360, 4450),
db::Point (2280, 4450),
db::Point (2280, 6120),
db::Point (7360, 6120),
db::Point (8760, 7490),
db::Point (13590, 17100),
db::Point (10280, 6120),
db::Point (26790, 13060),
db::Point (41270, 970)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
db::plc::ConvexDecompositionParameters param;
param.with_segments = true;
param.split_edges = false;
db::plc::Graph plc;
TestableConvexDecomposition decomp (&plc);
decomp.decompose (poly, param, dbu);
std::unique_ptr<db::Layout> ly (plc.to_layout ());
db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au5.gds");
}

View File

@ -0,0 +1,129 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dbPLC.h"
#include "tlUnitTest.h"
#include <list>
#include <memory>
namespace
{
class TestableGraph
: public db::plc::Graph
{
public:
using db::plc::Graph::Graph;
using db::plc::Graph::create_vertex;
using db::plc::Graph::create_edge;
using db::plc::Graph::create_triangle;
using db::plc::Graph::create_polygon;
};
}
TEST(basic)
{
TestableGraph plc;
db::plc::Vertex *v1 = plc.create_vertex (db::DPoint (1, 2));
EXPECT_EQ (v1->to_string (), "(1, 2)");
v1 = plc.create_vertex (db::DPoint (2, 1));
EXPECT_EQ (v1->to_string (), "(2, 1)");
EXPECT_EQ (v1->is_precious (), false);
v1->set_is_precious (true, 17);
EXPECT_EQ (v1->is_precious (), true);
EXPECT_EQ (v1->ids ().size (), 1u);
EXPECT_EQ (*v1->ids ().begin (), 17u);
v1->set_is_precious (true, 1);
EXPECT_EQ (v1->is_precious (), true);
EXPECT_EQ (v1->ids ().size (), 2u);
EXPECT_EQ (*v1->ids ().begin (), 1u);
}
TEST(edge)
{
TestableGraph plc;
db::plc::Vertex *v1 = plc.create_vertex (db::DPoint (1, 2));
db::plc::Vertex *v2 = plc.create_vertex (db::DPoint (3, 4));
db::plc::Edge *e = plc.create_edge (v1, v2);
EXPECT_EQ (e->to_string (), "((1, 2), (3, 4))");
EXPECT_EQ (v1->num_edges (), size_t (1));
EXPECT_EQ (v2->num_edges (), size_t (1));
EXPECT_EQ ((*v1->begin_edges ())->edge ().to_string (), "(1,2;3,4)");
EXPECT_EQ ((*v2->begin_edges ())->edge ().to_string (), "(1,2;3,4)");
}
TEST(polygon)
{
TestableGraph plc;
EXPECT_EQ (plc.num_polygons (), size_t (0));
EXPECT_EQ (plc.bbox ().to_string (), "()");
db::plc::Vertex *v1 = plc.create_vertex (db::DPoint (1, 2));
db::plc::Vertex *v2 = plc.create_vertex (db::DPoint (3, 4));
db::plc::Vertex *v3 = plc.create_vertex (db::DPoint (3, 2));
db::plc::Edge *e1 = plc.create_edge (v1, v2);
db::plc::Edge *e2 = plc.create_edge (v1, v3);
db::plc::Edge *e3 = plc.create_edge (v2, v3);
db::plc::Polygon *tri = plc.create_triangle (e1, e2, e3);
EXPECT_EQ (tri->to_string (), "((1, 2), (3, 4), (3, 2))");
EXPECT_EQ (tri->polygon ().to_string (), "(1,2;3,4;3,2)");
EXPECT_EQ (plc.bbox ().to_string (), "(1,2;3,4)");
EXPECT_EQ (plc.num_polygons (), size_t (1));
EXPECT_EQ (v1->num_edges (), size_t (2));
EXPECT_EQ (v2->num_edges (), size_t (2));
EXPECT_EQ (v3->num_edges (), size_t (2));
EXPECT_EQ (tri->edge (0) == e1, true);
EXPECT_EQ (tri->edge (3) == e1, true);
EXPECT_EQ (tri->edge (1) == e3, true);
EXPECT_EQ (tri->edge (2) == e2, true);
EXPECT_EQ (tri->edge (-1) == e2, true);
EXPECT_EQ (e1->left () == 0, true);
EXPECT_EQ (e1->right () == tri, true);
EXPECT_EQ (e2->left () == tri, true);
EXPECT_EQ (e2->right () == 0, true);
EXPECT_EQ (e3->left () == 0, true);
EXPECT_EQ (e3->right () == tri, true);
delete tri;
EXPECT_EQ (e1->left () == 0, true);
EXPECT_EQ (e1->right () == 0, true);
EXPECT_EQ (e2->left () == 0, true);
EXPECT_EQ (e2->right () == 0, true);
EXPECT_EQ (e3->left () == 0, true);
EXPECT_EQ (e3->right () == 0, true);
}

View File

@ -21,30 +21,65 @@
*/
#include "dbTriangle.h"
#include "dbPLC.h"
#include "tlUnitTest.h"
#include <list>
#include <memory>
class TestableTriangleEdge
: public db::TriangleEdge
class TestableEdge
: public db::plc::Edge
{
public:
using db::TriangleEdge::TriangleEdge;
using db::TriangleEdge::link;
using db::TriangleEdge::unlink;
using db::plc::Edge::Edge;
using db::plc::Edge::link;
using db::plc::Edge::unlink;
using db::plc::Edge::set_is_segment;
using db::plc::Edge::set_level;
using db::plc::Edge::level;
TestableTriangleEdge (db::Vertex *v1, db::Vertex *v2)
: db::TriangleEdge (v1, v2)
TestableEdge (db::plc::Vertex *v1, db::plc::Vertex *v2)
: db::plc::Edge (0, v1, v2)
{ }
};
class TestableVertex
: public db::plc::Vertex
{
public:
TestableVertex ()
: db::plc::Vertex (0)
{ }
TestableVertex (double x, double y)
: db::plc::Vertex (0, x, y)
{ }
TestableVertex (const db::DPoint &pt)
: db::plc::Vertex (0, pt)
{ }
};
class TestablePolygon
: public db::plc::Polygon
{
public:
TestablePolygon ()
: db::plc::Polygon (0)
{ }
TestablePolygon (db::plc::Edge *e1, db::plc::Edge *e2, db::plc::Edge *e3)
: db::plc::Polygon (0, e1, e2, e3)
{ }
using db::plc::Polygon::set_outside;
};
// Tests for Vertex class
TEST(Vertex_basic)
{
db::Vertex v;
TestableVertex v;
v.set_x (1.5);
v.set_y (0.5);
@ -52,11 +87,11 @@ TEST(Vertex_basic)
EXPECT_EQ (v.x (), 1.5);
EXPECT_EQ (v.y (), 0.5);
v = db::Vertex (db::DPoint (2, 3));
v = TestableVertex (db::DPoint (2, 3));
EXPECT_EQ (v.to_string (), "(2, 3)");
}
static std::string edges_from_vertex (const db::Vertex &v)
static std::string edges_from_vertex (const TestableVertex &v)
{
std::string res;
for (auto e = v.begin_edges (); e != v.end_edges (); ++e) {
@ -68,9 +103,9 @@ static std::string edges_from_vertex (const db::Vertex &v)
return res;
}
static std::string triangles_from_vertex (const db::Vertex &v)
static std::string triangles_from_vertex (const TestableVertex &v)
{
auto tri = v.triangles ();
auto tri = v.polygons ();
std::string res;
for (auto t = tri.begin (); t != tri.end (); ++t) {
if (! res.empty ()) {
@ -83,17 +118,17 @@ static std::string triangles_from_vertex (const db::Vertex &v)
TEST(Vertex_edge_registration)
{
db::Vertex v1 (0, 0);
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1 (0, 0);
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
std::unique_ptr<TestableTriangleEdge> e1 (new TestableTriangleEdge (&v1, &v2));
std::unique_ptr<TestableEdge> e1 (new TestableEdge (&v1, &v2));
e1->link ();
EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))");
EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2))");
EXPECT_EQ (edges_from_vertex (v3), "");
std::unique_ptr<TestableTriangleEdge> e2 (new TestableTriangleEdge (&v2, &v3));
std::unique_ptr<TestableEdge> e2 (new TestableEdge (&v2, &v3));
e2->link ();
EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))");
EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2)), ((1, 2), (2, 1))");
@ -114,29 +149,29 @@ TEST(Vertex_edge_registration)
TEST(Vertex_triangles)
{
db::Vertex v1 (0, 0);
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
db::Vertex v4 (-1, 2);
TestableVertex v1 (0, 0);
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableVertex v4 (-1, 2);
EXPECT_EQ (triangles_from_vertex (v1), "");
std::unique_ptr<TestableTriangleEdge> e1 (new TestableTriangleEdge (&v1, &v2));
std::unique_ptr<TestableEdge> e1 (new TestableEdge (&v1, &v2));
e1->link ();
std::unique_ptr<TestableTriangleEdge> e2 (new TestableTriangleEdge (&v2, &v3));
std::unique_ptr<TestableEdge> e2 (new TestableEdge (&v2, &v3));
e2->link ();
std::unique_ptr<TestableTriangleEdge> e3 (new TestableTriangleEdge (&v3, &v1));
std::unique_ptr<TestableEdge> e3 (new TestableEdge (&v3, &v1));
e3->link ();
std::unique_ptr<db::Triangle> tri (new db::Triangle (e1.get (), e2.get (), e3.get ()));
std::unique_ptr<TestablePolygon> tri (new TestablePolygon (e1.get (), e2.get (), e3.get ()));
EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (1, 2), (2, 1))");
EXPECT_EQ (triangles_from_vertex (v2), "((0, 0), (1, 2), (2, 1))");
EXPECT_EQ (triangles_from_vertex (v3), "((0, 0), (1, 2), (2, 1))");
std::unique_ptr<TestableTriangleEdge> e4 (new TestableTriangleEdge (&v1, &v4));
std::unique_ptr<TestableEdge> e4 (new TestableEdge (&v1, &v4));
e4->link ();
std::unique_ptr<TestableTriangleEdge> e5 (new TestableTriangleEdge (&v2, &v4));
std::unique_ptr<TestableEdge> e5 (new TestableEdge (&v2, &v4));
e5->link ();
std::unique_ptr<db::Triangle> tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ()));
std::unique_ptr<TestablePolygon> tri2 (new TestablePolygon (e1.get (), e4.get (), e5.get ()));
EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (-1, 2), (1, 2)), ((0, 0), (1, 2), (2, 1))");
EXPECT_EQ (triangles_from_vertex (v2), "((0, 0), (-1, 2), (1, 2)), ((0, 0), (1, 2), (2, 1))");
EXPECT_EQ (triangles_from_vertex (v3), "((0, 0), (1, 2), (2, 1))");
@ -153,18 +188,18 @@ TEST(Vertex_triangles)
TEST(Triangle_basic)
{
db::Vertex v1;
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1;
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
EXPECT_EQ (s1.v1 () == &v1, true);
EXPECT_EQ (s2.v2 () == &v3, true);
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
EXPECT_EQ (tri.to_string (), "((0, 0), (1, 2), (2, 1))");
EXPECT_EQ (tri.edge (-1) == &s3, true);
EXPECT_EQ (tri.edge (0) == &s1, true);
@ -172,11 +207,11 @@ TEST(Triangle_basic)
EXPECT_EQ (tri.edge (3) == &s1, true);
// ordering
TestableTriangleEdge s11 (&v1, &v2);
TestableTriangleEdge s12 (&v3, &v2);
TestableTriangleEdge s13 (&v1, &v3);
TestableEdge s11 (&v1, &v2);
TestableEdge s12 (&v3, &v2);
TestableEdge s13 (&v1, &v3);
db::Triangle tri2 (&s11, &s12, &s13);
TestablePolygon tri2 (&s11, &s12, &s13);
EXPECT_EQ (tri2.to_string (), "((0, 0), (1, 2), (2, 1))");
// triangle registration
@ -201,15 +236,15 @@ TEST(Triangle_basic)
TEST(Triangle_find_segment_with)
{
db::Vertex v1;
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1;
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
EXPECT_EQ (tri.find_edge_with (&v1, &v2)->to_string (), "((0, 0), (1, 2))");
EXPECT_EQ (tri.find_edge_with (&v2, &v1)->to_string (), "((0, 0), (1, 2))");
@ -217,15 +252,15 @@ TEST(Triangle_find_segment_with)
TEST(Triangle_ext_vertex)
{
db::Vertex v1;
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1;
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
EXPECT_EQ (tri.opposite (&s1)->to_string (), "(2, 1)");
EXPECT_EQ (tri.opposite (&s3)->to_string (), "(1, 2)");
@ -233,15 +268,15 @@ TEST(Triangle_ext_vertex)
TEST(Triangle_opposite_vertex)
{
db::Vertex v1;
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1;
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
EXPECT_EQ (tri.opposite (&s1)->to_string (), "(2, 1)");
EXPECT_EQ (tri.opposite (&s3)->to_string (), "(1, 2)");
@ -249,15 +284,15 @@ TEST(Triangle_opposite_vertex)
TEST(Triangle_opposite_edge)
{
db::Vertex v1;
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1;
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
EXPECT_EQ (tri.opposite (&v1)->to_string (), "((1, 2), (2, 1))");
EXPECT_EQ (tri.opposite (&v3)->to_string (), "((0, 0), (1, 2))");
@ -265,16 +300,16 @@ TEST(Triangle_opposite_edge)
TEST(Triangle_contains)
{
db::Vertex v1;
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1;
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
{
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
EXPECT_EQ (tri.contains (db::DPoint (0, 0)), 0);
EXPECT_EQ (tri.contains (db::DPoint (-1, -2)), -1);
EXPECT_EQ (tri.contains (db::DPoint (0.5, 1)), 0);
@ -289,7 +324,7 @@ TEST(Triangle_contains)
s3.reverse ();
{
db::Triangle tri2 (&s3, &s2, &s1);
TestablePolygon tri2 (&s3, &s2, &s1);
EXPECT_EQ (tri2.contains(db::DPoint(0, 0)), 0);
EXPECT_EQ (tri2.contains(db::DPoint(0.5, 1)), 0);
EXPECT_EQ (tri2.contains(db::DPoint(0.5, 2)), -1);
@ -301,16 +336,16 @@ TEST(Triangle_contains)
TEST(Triangle_contains_small)
{
db::Vertex v1;
db::Vertex v2 (0.001, 0.002);
db::Vertex v3 (0.002, 0.001);
TestableVertex v1;
TestableVertex v2 (0.001, 0.002);
TestableVertex v3 (0.002, 0.001);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
{
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
EXPECT_EQ (tri.contains (db::DPoint (0, 0)), 0);
EXPECT_EQ (tri.contains (db::DPoint (-0.001, -0.002)), -1);
EXPECT_EQ (tri.contains (db::DPoint (0.0005, 0.001)), 0);
@ -325,7 +360,7 @@ TEST(Triangle_contains_small)
s3.reverse ();
{
db::Triangle tri2 (&s3, &s2, &s1);
TestablePolygon tri2 (&s3, &s2, &s1);
EXPECT_EQ (tri2.contains(db::DPoint(0, 0)), 0);
EXPECT_EQ (tri2.contains(db::DPoint(0.0005, 0.001)), 0);
EXPECT_EQ (tri2.contains(db::DPoint(0.0005, 0.002)), -1);
@ -337,15 +372,15 @@ TEST(Triangle_contains_small)
TEST(Triangle_circumcircle)
{
db::Vertex v1;
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
TestableVertex v1;
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v2, &v3);
TestableTriangleEdge s3 (&v3, &v1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v2, &v3);
TestableEdge s3 (&v3, &v1);
db::Triangle tri (&s1, &s2, &s3);
TestablePolygon tri (&s1, &s2, &s3);
auto cc = tri.circumcircle ();
auto center = cc.first;
@ -354,8 +389,8 @@ TEST(Triangle_circumcircle)
EXPECT_EQ (tl::to_string (center), "0.833333333333,0.833333333333");
EXPECT_EQ (tl::to_string (radius), "1.17851130198");
EXPECT_EQ (db::Vertex::in_circle (center, center, radius), 1);
EXPECT_EQ (db::Vertex::in_circle (db::DPoint (-1, -1), center, radius), -1);
EXPECT_EQ (TestableVertex::in_circle (center, center, radius), 1);
EXPECT_EQ (TestableVertex::in_circle (db::DPoint (-1, -1), center, radius), -1);
EXPECT_EQ (v1.in_circle (center, radius), 0);
EXPECT_EQ (v2.in_circle (center, radius), 0);
EXPECT_EQ (v3.in_circle (center, radius), 0);
@ -365,10 +400,10 @@ TEST(Triangle_circumcircle)
TEST(TriangleEdge_basic)
{
db::Vertex v1;
db::Vertex v2 (1, 0.5);
TestableVertex v1;
TestableVertex v2 (1, 0.5);
TestableTriangleEdge edge (&v1, &v2);
TestableEdge edge (&v1, &v2);
EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))");
EXPECT_EQ (edge.is_segment (), false);
@ -385,20 +420,20 @@ TEST(TriangleEdge_basic)
TEST(TriangleEdge_triangles)
{
db::Vertex v1 (0, 0);
db::Vertex v2 (1, 2);
db::Vertex v3 (2, 1);
db::Vertex v4 (-1, 2);
TestableVertex v1 (0, 0);
TestableVertex v2 (1, 2);
TestableVertex v3 (2, 1);
TestableVertex v4 (-1, 2);
std::unique_ptr<TestableTriangleEdge> e1 (new TestableTriangleEdge (&v1, &v2));
std::unique_ptr<TestableTriangleEdge> e2 (new TestableTriangleEdge (&v2, &v3));
std::unique_ptr<TestableTriangleEdge> e3 (new TestableTriangleEdge (&v3, &v1));
std::unique_ptr<TestableEdge> e1 (new TestableEdge (&v1, &v2));
std::unique_ptr<TestableEdge> e2 (new TestableEdge (&v2, &v3));
std::unique_ptr<TestableEdge> e3 (new TestableEdge (&v3, &v1));
std::unique_ptr<db::Triangle> tri (new db::Triangle (e1.get (), e2.get (), e3.get ()));
std::unique_ptr<TestablePolygon> tri (new TestablePolygon (e1.get (), e2.get (), e3.get ()));
std::unique_ptr<TestableTriangleEdge> e4 (new TestableTriangleEdge (&v1, &v4));
std::unique_ptr<TestableTriangleEdge> e5 (new TestableTriangleEdge (&v2, &v4));
std::unique_ptr<db::Triangle> tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ()));
std::unique_ptr<TestableEdge> e4 (new TestableEdge (&v1, &v4));
std::unique_ptr<TestableEdge> e5 (new TestableEdge (&v2, &v4));
std::unique_ptr<TestablePolygon> tri2 (new TestablePolygon (e1.get (), e4.get (), e5.get ()));
EXPECT_EQ (e1->is_outside (), false);
EXPECT_EQ (e2->is_outside (), true);
@ -408,10 +443,10 @@ TEST(TriangleEdge_triangles)
tri->set_outside (true);
EXPECT_EQ (e1->is_for_outside_triangles (), true);
EXPECT_EQ (e1->has_triangle (tri.get ()), true);
EXPECT_EQ (e1->has_triangle (tri2.get ()), true);
EXPECT_EQ (e4->has_triangle (tri.get ()), false);
EXPECT_EQ (e4->has_triangle (tri2.get ()), true);
EXPECT_EQ (e1->has_polygon (tri.get ()), true);
EXPECT_EQ (e1->has_polygon (tri2.get ()), true);
EXPECT_EQ (e4->has_polygon (tri.get ()), false);
EXPECT_EQ (e4->has_polygon (tri2.get ()), true);
EXPECT_EQ (e1->other (tri.get ()) == tri2.get (), true);
EXPECT_EQ (e1->other (tri2.get ()) == tri.get (), true);
@ -420,43 +455,43 @@ TEST(TriangleEdge_triangles)
EXPECT_EQ (e2->common_vertex (e4.get ()) == 0, true);
tri->unlink ();
EXPECT_EQ (e1->has_triangle (tri.get ()), false);
EXPECT_EQ (e1->has_triangle (tri2.get ()), true);
EXPECT_EQ (e1->has_polygon (tri.get ()), false);
EXPECT_EQ (e1->has_polygon (tri2.get ()), true);
}
TEST(TriangleEdge_side_of)
{
db::Vertex v1;
db::Vertex v2 (1, 0.5);
TestableVertex v1;
TestableVertex v2 (1, 0.5);
TestableTriangleEdge edge (&v1, &v2);
TestableEdge edge (&v1, &v2);
EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))");
EXPECT_EQ (edge.side_of (db::Vertex (0, 0)), 0)
EXPECT_EQ (edge.side_of (db::Vertex (0.5, 0.25)), 0)
EXPECT_EQ (edge.side_of (db::Vertex (0, 1)), -1)
EXPECT_EQ (edge.side_of (db::Vertex (0, -1)), 1)
EXPECT_EQ (edge.side_of (db::Vertex (0.5, 0.5)), -1)
EXPECT_EQ (edge.side_of (db::Vertex (0.5, 0)), 1)
EXPECT_EQ (edge.side_of (TestableVertex (0, 0)), 0)
EXPECT_EQ (edge.side_of (TestableVertex (0.5, 0.25)), 0)
EXPECT_EQ (edge.side_of (TestableVertex (0, 1)), -1)
EXPECT_EQ (edge.side_of (TestableVertex (0, -1)), 1)
EXPECT_EQ (edge.side_of (TestableVertex (0.5, 0.5)), -1)
EXPECT_EQ (edge.side_of (TestableVertex (0.5, 0)), 1)
db::Vertex v3 (1, 0);
db::Vertex v4 (0, 1);
TestableTriangleEdge edge2 (&v3, &v4);
TestableVertex v3 (1, 0);
TestableVertex v4 (0, 1);
TestableEdge edge2 (&v3, &v4);
EXPECT_EQ (edge2.side_of (db::Vertex(0.2, 0.2)), -1);
EXPECT_EQ (edge2.side_of (TestableVertex(0.2, 0.2)), -1);
}
namespace {
class VertexHeap
{
public:
db::Vertex *make_vertex (double x, double y)
TestableVertex *make_vertex (double x, double y)
{
m_heap.push_back (db::Vertex (x, y));
m_heap.push_back (TestableVertex (x, y));
return &m_heap.back ();
}
private:
std::list<db::Vertex> m_heap;
std::list<TestableVertex> m_heap;
};
}
@ -464,26 +499,26 @@ TEST(TriangleEdge_crosses)
{
VertexHeap heap;
TestableTriangleEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5));
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false);
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), false); // only cuts
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(1, 0.5))), false);
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(2, 0.5))), false);
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true);
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(-0.1, 0.5))), false);
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.6), heap.make_vertex(0, 0.6))), false);
EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 1), heap.make_vertex(1, 1))), false);
TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5));
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false);
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), false); // only cuts
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(1, 0.5))), false);
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(2, 0.5))), false);
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true);
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(-0.1, 0.5))), false);
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.6), heap.make_vertex(0, 0.6))), false);
EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 1), heap.make_vertex(1, 1))), false);
EXPECT_EQ (s1.crosses_including (TestableTriangleEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false);
EXPECT_EQ (s1.crosses_including (TestableTriangleEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), true); // only cuts
EXPECT_EQ (s1.crosses_including (TestableTriangleEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true);
EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false);
EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), true); // only cuts
EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true);
}
TEST(TriangleEdge_point_on)
{
VertexHeap heap;
TestableTriangleEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5));
TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5));
EXPECT_EQ (s1.point_on (db::DPoint (0, 0)), false); // endpoints are not "on"
EXPECT_EQ (s1.point_on (db::DPoint (0, -0.5)), false);
EXPECT_EQ (s1.point_on (db::DPoint (0.5, 0)), false);
@ -497,23 +532,23 @@ TEST(TriangleEdge_intersection_point)
{
VertexHeap heap;
TestableTriangleEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5));
EXPECT_EQ (s1.intersection_point (TestableTriangleEdge (heap.make_vertex (-1, 0.25), heap.make_vertex (2, 0.25))).to_string (), "0.5,0.25");
TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5));
EXPECT_EQ (s1.intersection_point (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex (2, 0.25))).to_string (), "0.5,0.25");
}
TEST(TriangleEdge_can_flip)
{
db::Vertex v1 (2, -1);
db::Vertex v2 (0, 0);
db::Vertex v3 (1, 0);
db::Vertex v4 (0.5, 1);
TestableTriangleEdge s1 (&v1, &v2);
TestableTriangleEdge s2 (&v1, &v3);
TestableTriangleEdge s3 (&v2, &v3);
TestableTriangleEdge s4 (&v2, &v4);
TestableTriangleEdge s5 (&v3, &v4);
db::Triangle t1 (&s1, &s2, &s3);
db::Triangle t2 (&s3, &s4, &s5);
TestableVertex v1 (2, -1);
TestableVertex v2 (0, 0);
TestableVertex v3 (1, 0);
TestableVertex v4 (0.5, 1);
TestableEdge s1 (&v1, &v2);
TestableEdge s2 (&v1, &v3);
TestableEdge s3 (&v2, &v3);
TestableEdge s4 (&v2, &v4);
TestableEdge s5 (&v3, &v4);
TestablePolygon t1 (&s1, &s2, &s3);
TestablePolygon t2 (&s3, &s4, &s5);
EXPECT_EQ (s3.left () == &t2, true);
EXPECT_EQ (s3.right () == &t1, true);
EXPECT_EQ (s3.can_flip(), false);
@ -529,10 +564,10 @@ TEST(TriangleEdge_can_flip)
TEST(TriangleEdge_distance)
{
db::Vertex v1 (0, 0);
db::Vertex v2 (1, 0);
TestableVertex v1 (0, 0);
TestableVertex v2 (1, 0);
TestableTriangleEdge seg (&v1, &v2);
TestableEdge seg (&v1, &v2);
EXPECT_EQ (seg.distance (db::DPoint (0, 0)), 0);
EXPECT_EQ (seg.distance (db::DPoint (0, 1)), 1);
EXPECT_EQ (seg.distance (db::DPoint (1, 2)), 2);

View File

@ -21,7 +21,7 @@
*/
#include "dbTriangles.h"
#include "dbPLCTriangulation.h"
#include "dbWriter.h"
#include "dbRegionProcessors.h"
#include "tlUnitTest.h"
@ -33,56 +33,73 @@
#include <cstdlib>
#include <cmath>
class TestableTriangles
: public db::Triangles
namespace
{
class TestableTriangulation
: public db::plc::Triangulation
{
public:
using db::Triangles::Triangles;
using db::Triangles::check;
using db::Triangles::dump;
using db::Triangles::flip;
using db::Triangles::insert_point;
using db::Triangles::search_edges_crossing;
using db::Triangles::find_edge_for_points;
using db::Triangles::find_points_around;
using db::Triangles::find_inside_circle;
using db::Triangles::create_constrained_delaunay;
using db::Triangles::is_illegal_edge;
using db::Triangles::find_vertex_for_point;
using db::Triangles::remove;
using db::Triangles::ensure_edge;
using db::Triangles::constrain;
using db::Triangles::remove_outside_triangles;
using db::plc::Triangulation::Triangulation;
using db::plc::Triangulation::check;
using db::plc::Triangulation::flip;
using db::plc::Triangulation::insert_point;
using db::plc::Triangulation::search_edges_crossing;
using db::plc::Triangulation::find_edge_for_points;
using db::plc::Triangulation::find_points_around;
using db::plc::Triangulation::find_inside_circle;
using db::plc::Triangulation::create_constrained_delaunay;
using db::plc::Triangulation::is_illegal_edge;
using db::plc::Triangulation::find_vertex_for_point;
using db::plc::Triangulation::remove;
using db::plc::Triangulation::ensure_edge;
using db::plc::Triangulation::constrain;
using db::plc::Triangulation::remove_outside_triangles;
};
class TestableGraph
: public db::plc::Graph
{
public:
using db::plc::Graph::Graph;
using db::plc::Graph::create_vertex;
using db::plc::Graph::create_edge;
using db::plc::Graph::create_triangle;
};
}
TEST(basic)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.init_box (db::DBox (1, 0, 5, 4));
EXPECT_EQ (tris.bbox ().to_string (), "(1,0;5,4)");
EXPECT_EQ (tris.to_string (), "((1, 0), (1, 4), (5, 0)), ((1, 4), (5, 4), (5, 0))");
EXPECT_EQ (plc.bbox ().to_string (), "(1,0;5,4)");
EXPECT_EQ (plc.to_string (), "((1, 0), (1, 4), (5, 0)), ((1, 4), (5, 4), (5, 0))");
EXPECT_EQ (tris.check (), true);
tris.clear ();
EXPECT_EQ (tris.bbox ().to_string (), "()");
EXPECT_EQ (tris.to_string (), "");
EXPECT_EQ (plc.bbox ().to_string (), "()");
EXPECT_EQ (plc.to_string (), "");
EXPECT_EQ (tris.check (), true);
}
TEST(flip)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.init_box (db::DBox (0, 0, 1, 1));
EXPECT_EQ (tris.to_string (), "((0, 0), (0, 1), (1, 0)), ((0, 1), (1, 1), (1, 0))");
EXPECT_EQ (plc.to_string (), "((0, 0), (0, 1), (1, 0)), ((0, 1), (1, 1), (1, 0))");
EXPECT_EQ (tris.num_triangles (), size_t (2));
EXPECT_EQ (plc.num_polygons (), size_t (2));
EXPECT_EQ (tris.check (), true);
const db::Triangle &t1 = *tris.begin ();
db::TriangleEdge *diag_segment;
const db::plc::Polygon &t1 = *plc.begin ();
db::plc::Edge *diag_segment;
for (int i = 0; i < 3; ++i) {
diag_segment = t1.edge (i);
if (diag_segment->side_of (db::DPoint (0.5, 0.5)) == 0) {
@ -90,85 +107,126 @@ TEST(flip)
}
}
tris.flip (diag_segment);
EXPECT_EQ (tris.to_string (), "((1, 1), (0, 0), (0, 1)), ((1, 1), (1, 0), (0, 0))");
EXPECT_EQ (plc.to_string (), "((1, 1), (0, 0), (0, 1)), ((1, 1), (1, 0), (0, 0))");
EXPECT_EQ (tris.check (), true);
}
TEST(insert)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.init_box (db::DBox (0, 0, 1, 1));
tris.insert_point (0.2, 0.2);
EXPECT_EQ (tris.to_string (), "((0, 0), (0, 1), (0.2, 0.2)), ((1, 0), (0, 0), (0.2, 0.2)), ((1, 1), (0.2, 0.2), (0, 1)), ((1, 1), (1, 0), (0.2, 0.2))");
EXPECT_EQ (plc.to_string (), "((0, 0), (0, 1), (0.2, 0.2)), ((1, 0), (0, 0), (0.2, 0.2)), ((1, 1), (0.2, 0.2), (0, 1)), ((1, 1), (1, 0), (0.2, 0.2))");
EXPECT_EQ (tris.check (), true);
}
TEST(split_segment)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.init_box (db::DBox (0, 0, 1, 1));
tris.insert_point (0.5, 0.5);
EXPECT_EQ (tris.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))");
EXPECT_EQ (plc.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))");
EXPECT_EQ (tris.check(), true);
}
TEST(insert_vertex_twice)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.init_box (db::DBox (0, 0, 1, 1));
tris.insert_point (0.5, 0.5);
// inserted a vertex twice does not change anything
tris.insert_point (0.5, 0.5);
EXPECT_EQ (tris.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))");
EXPECT_EQ (plc.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))");
EXPECT_EQ (tris.check(), true);
}
TEST(collect_vertexes)
{
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.init_box (db::DBox (0, 0, 1, 1));
tris.insert_point (0.2, 0.2);
tris.insert_point (0.5, 0.5);
std::vector<db::plc::Vertex *> vertexes = tris.find_vertexes_along_line (db::DPoint (0, 0), db::DPoint (1.5, 1.5));
EXPECT_EQ (vertexes.size (), size_t (4));
if (vertexes.size () >= size_t (4)) {
EXPECT_EQ (vertexes [0]->to_string (), "(0, 0)");
EXPECT_EQ (vertexes [1]->to_string (), "(0.2, 0.2)");
EXPECT_EQ (vertexes [2]->to_string (), "(0.5, 0.5)");
EXPECT_EQ (vertexes [3]->to_string (), "(1, 1)");
}
vertexes = tris.find_vertexes_along_line (db::DPoint (0, 0), db::DPoint (1.0, 1.0));
EXPECT_EQ (vertexes.size (), size_t (4));
if (vertexes.size () >= size_t (4)) {
EXPECT_EQ (vertexes [0]->to_string (), "(0, 0)");
EXPECT_EQ (vertexes [1]->to_string (), "(0.2, 0.2)");
EXPECT_EQ (vertexes [2]->to_string (), "(0.5, 0.5)");
EXPECT_EQ (vertexes [3]->to_string (), "(1, 1)");
}
vertexes = tris.find_vertexes_along_line (db::DPoint (1, 1), db::DPoint (0.25, 0.25));
EXPECT_EQ (vertexes.size (), size_t (2));
if (vertexes.size () >= size_t (2)) {
EXPECT_EQ (vertexes [0]->to_string (), "(1, 1)");
EXPECT_EQ (vertexes [1]->to_string (), "(0.5, 0.5)");
}
}
TEST(insert_vertex_convex)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.insert_point (0.2, 0.2);
tris.insert_point (0.2, 0.8);
tris.insert_point (0.6, 0.5);
tris.insert_point (0.7, 0.5);
tris.insert_point (0.6, 0.4);
EXPECT_EQ (tris.to_string (), "((0.2, 0.2), (0.2, 0.8), (0.6, 0.5)), ((0.2, 0.8), (0.7, 0.5), (0.6, 0.5)), ((0.6, 0.4), (0.6, 0.5), (0.7, 0.5)), ((0.6, 0.4), (0.2, 0.2), (0.6, 0.5))");
EXPECT_EQ (plc.to_string (), "((0.2, 0.2), (0.2, 0.8), (0.6, 0.5)), ((0.2, 0.8), (0.7, 0.5), (0.6, 0.5)), ((0.6, 0.4), (0.6, 0.5), (0.7, 0.5)), ((0.6, 0.4), (0.2, 0.2), (0.6, 0.5))");
EXPECT_EQ (tris.check(), true);
}
TEST(insert_vertex_convex2)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.insert_point (0.25, 0.1);
tris.insert_point (0.1, 0.4);
tris.insert_point (0.4, 0.15);
tris.insert_point (1, 0.7);
EXPECT_EQ (tris.to_string (), "((0.25, 0.1), (0.1, 0.4), (0.4, 0.15)), ((1, 0.7), (0.4, 0.15), (0.1, 0.4))");
EXPECT_EQ (plc.to_string (), "((0.25, 0.1), (0.1, 0.4), (0.4, 0.15)), ((1, 0.7), (0.4, 0.15), (0.1, 0.4))");
EXPECT_EQ (tris.check(), true);
}
TEST(insert_vertex_convex3)
{
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.insert_point (0.25, 0.5);
tris.insert_point (0.25, 0.55);
tris.insert_point (0.15, 0.8);
tris.insert_point (1, 0.4);
EXPECT_EQ (tris.to_string (), "((0.25, 0.5), (0.15, 0.8), (0.25, 0.55)), ((1, 0.4), (0.25, 0.5), (0.25, 0.55)), ((0.15, 0.8), (1, 0.4), (0.25, 0.55))");
EXPECT_EQ (plc.to_string (), "((0.25, 0.5), (0.15, 0.8), (0.25, 0.55)), ((1, 0.4), (0.25, 0.5), (0.25, 0.55)), ((0.15, 0.8), (1, 0.4), (0.25, 0.55))");
EXPECT_EQ (tris.check(), true);
}
TEST(search_edges_crossing)
{
TestableTriangles tris;
db::Vertex *v1 = tris.insert_point (0.2, 0.2);
db::Vertex *v2 = tris.insert_point (0.2, 0.8);
db::Vertex *v3 = tris.insert_point (0.6, 0.5);
/*db::Vertex *v4 =*/ tris.insert_point (0.7, 0.5);
db::Vertex *v5 = tris.insert_point (0.6, 0.4);
db::Vertex *v6 = tris.insert_point (0.7, 0.2);
db::plc::Graph plc;
TestableTriangulation tris (&plc);
db::plc::Vertex *v1 = tris.insert_point (0.2, 0.2);
db::plc::Vertex *v2 = tris.insert_point (0.2, 0.8);
db::plc::Vertex *v3 = tris.insert_point (0.6, 0.5);
/*db::plc::Vertex *v4 =*/ tris.insert_point (0.7, 0.5);
db::plc::Vertex *v5 = tris.insert_point (0.6, 0.4);
db::plc::Vertex *v6 = tris.insert_point (0.7, 0.2);
EXPECT_EQ (tris.check(), true);
auto xedges = tris.search_edges_crossing (v2, v6);
@ -182,80 +240,84 @@ TEST(search_edges_crossing)
TEST(illegal_edge1)
{
db::Vertex v1 (0, 0);
db::Vertex v2 (1.6, 1.6);
db::Vertex v3 (1, 2);
db::Vertex v4 (2, 1);
TestableGraph plc;
db::plc::Vertex *v1 = plc.create_vertex (0, 0);
db::plc::Vertex *v2 = plc.create_vertex (1.6, 1.6);
db::plc::Vertex *v3 = plc.create_vertex (1, 2);
db::plc::Vertex *v4 = plc.create_vertex (2, 1);
{
db::TriangleEdge e1 (&v1, &v3);
db::TriangleEdge e2 (&v3, &v4);
db::TriangleEdge e3 (&v4, &v1);
db::plc::Edge *e1 = plc.create_edge (v1, v3);
db::plc::Edge *e2 = plc.create_edge (v3, v4);
db::plc::Edge *e3 = plc.create_edge (v4, v1);
db::Triangle t1 (&e1, &e2, &e3);
plc.create_triangle (e1, e2, e3);
db::TriangleEdge ee1 (&v2, &v3);
db::TriangleEdge ee2 (&v4, &v2);
db::plc::Edge *ee1 = plc.create_edge (v2, v3);
db::plc::Edge *ee2 = plc.create_edge (v4, v2);
db::Triangle t2 (&ee1, &e2, &ee2);
plc.create_triangle (ee1, e2, ee2);
EXPECT_EQ (TestableTriangles::is_illegal_edge (&e2), true);
EXPECT_EQ (TestableTriangulation::is_illegal_edge (e2), true);
}
{
// flipped
db::TriangleEdge e1 (&v1, &v2);
db::TriangleEdge e2 (&v2, &v3);
db::TriangleEdge e3 (&v3, &v1);
db::plc::Edge *e1 = plc.create_edge (v1, v2);
db::plc::Edge *e2 = plc.create_edge (v2, v3);
db::plc::Edge *e3 = plc.create_edge (v3, v1);
db::Triangle t1 (&e1, &e2, &e3);
plc.create_triangle (e1, e2, e3);
db::TriangleEdge ee1 (&v1, &v4);
db::TriangleEdge ee2 (&v4, &v2);
db::plc::Edge *ee1 = plc.create_edge (v1, v4);
db::plc::Edge *ee2 = plc.create_edge (v4, v2);
db::Triangle t2 (&ee1, &ee2, &e1);
plc.create_triangle (ee1, ee2, e1);
EXPECT_EQ (TestableTriangles::is_illegal_edge (&e2), false);
EXPECT_EQ (TestableTriangulation::is_illegal_edge (e2), false);
}
}
TEST(illegal_edge2)
{
TestableGraph plc;
// numerical border case
db::Vertex v1 (773.94756216690905, 114.45875269431208);
db::Vertex v2 (773.29574734131643, 113.47402096138073);
db::Vertex v3 (773.10652961562653, 114.25497975904504);
db::Vertex v4 (774.08856345337881, 113.60495072750861);
db::plc::Vertex *v1 = plc.create_vertex (773.94756216690905, 114.45875269431208);
db::plc::Vertex *v2 = plc.create_vertex (773.29574734131643, 113.47402096138073);
db::plc::Vertex *v3 = plc.create_vertex (773.10652961562653, 114.25497975904504);
db::plc::Vertex *v4 = plc.create_vertex (774.08856345337881, 113.60495072750861);
{
db::TriangleEdge e1 (&v1, &v2);
db::TriangleEdge e2 (&v2, &v4);
db::TriangleEdge e3 (&v4, &v1);
db::plc::Edge *e1 = plc.create_edge (v1, v2);
db::plc::Edge *e2 = plc.create_edge (v2, v4);
db::plc::Edge *e3 = plc.create_edge (v4, v1);
db::Triangle t1 (&e1, &e2, &e3);
plc.create_triangle (e1, e2, e3);
db::TriangleEdge ee1 (&v2, &v3);
db::TriangleEdge ee2 (&v3, &v4);
db::plc::Edge *ee1 = plc.create_edge (v2, v3);
db::plc::Edge *ee2 = plc.create_edge (v3, v4);
db::Triangle t2 (&ee1, &ee2, &e2);
plc.create_triangle (ee1, ee2, e2);
EXPECT_EQ (TestableTriangles::is_illegal_edge (&e2), false);
EXPECT_EQ (TestableTriangulation::is_illegal_edge (e2), false);
}
{
// flipped
db::TriangleEdge e1 (&v1, &v2);
db::TriangleEdge e2 (&v2, &v3);
db::TriangleEdge e3 (&v3, &v1);
db::plc::Edge *e1 = plc.create_edge (v1, v2);
db::plc::Edge *e2 = plc.create_edge (v2, v3);
db::plc::Edge *e3 = plc.create_edge (v3, v1);
db::Triangle t1 (&e1, &e2, &e3);
plc.create_triangle (e1, e2, e3);
db::TriangleEdge ee1 (&v1, &v4);
db::TriangleEdge ee2 (&v4, &v2);
db::plc::Edge *ee1 = plc.create_edge (v1, v4);
db::plc::Edge *ee2 = plc.create_edge (v4, v2);
db::Triangle t2 (&ee1, &ee2, &e1);
plc.create_triangle (ee1, ee2, e1);
EXPECT_EQ (TestableTriangles::is_illegal_edge (&e1), false);
EXPECT_EQ (TestableTriangulation::is_illegal_edge (e1), false);
}
}
@ -279,7 +341,8 @@ TEST(insert_many)
{
srand (0);
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
double res = 65536.0;
db::DBox bbox;
@ -291,6 +354,7 @@ TEST(insert_many)
tris.insert_point (x, y);
}
// slow: EXPECT_EQ (tris.check (), true);
EXPECT_LT (double (tris.flips ()) / double (n), 3.1);
EXPECT_LT (double (tris.hops ()) / double (n), 23.0);
}
@ -304,7 +368,8 @@ TEST(heavy_insert)
srand (l);
tl::info << "." << tl::noendl;
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
double res = 128.0;
unsigned int n = rand () % 190 + 10;
@ -315,17 +380,17 @@ TEST(heavy_insert)
for (unsigned int i = 0; i < n; ++i) {
double x = round (flt_rand () * res) * (1.0 / res);
double y = round (flt_rand () * res) * (1.0 / res);
db::Vertex *v = tris.insert_point (x, y);
db::plc::Vertex *v = tris.insert_point (x, y);
bbox += db::DPoint (x, y);
vmap.insert (std::make_pair (*v, false));
vmap.insert (std::pair<db::DPoint, bool> (*v, false));
}
// not strictly true, but very likely with at least 10 vertexes:
EXPECT_GT (tris.num_triangles (), size_t (0));
EXPECT_EQ (tris.bbox ().to_string (), bbox.to_string ());
EXPECT_GT (plc.num_polygons (), size_t (0));
EXPECT_EQ (plc.bbox ().to_string (), bbox.to_string ());
bool ok = true;
for (auto t = tris.begin (); t != tris.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
for (int i = 0; i < 3; ++i) {
auto f = vmap.find (*t->vertex (i));
if (f == vmap.end ()) {
@ -360,7 +425,8 @@ TEST(heavy_remove)
srand (l);
tl::info << "." << tl::noendl;
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
double res = 128.0;
unsigned int n = rand () % 190 + 10;
@ -373,11 +439,11 @@ TEST(heavy_remove)
EXPECT_EQ (tris.check(), true);
std::set<db::Vertex *> vset;
std::vector<db::Vertex *> vertexes;
for (auto t = tris.begin (); t != tris.end (); ++t) {
std::set<db::plc::Vertex *> vset;
std::vector<db::plc::Vertex *> vertexes;
for (auto t = plc.begin (); t != plc.end (); ++t) {
for (int i = 0; i < 3; ++i) {
db::Vertex *v = t->vertex (i);
db::plc::Vertex *v = t->vertex (i);
if (vset.insert (v).second) {
vertexes.push_back (v);
}
@ -387,7 +453,7 @@ TEST(heavy_remove)
while (! vertexes.empty ()) {
unsigned int n = rand () % (unsigned int) vertexes.size ();
db::Vertex *v = vertexes [n];
db::plc::Vertex *v = vertexes [n];
tris.remove (v);
vertexes.erase (vertexes.begin () + n);
@ -398,7 +464,7 @@ TEST(heavy_remove)
}
EXPECT_EQ (tris.num_triangles (), size_t (0));
EXPECT_EQ (plc.num_polygons (), size_t (0));
}
@ -409,7 +475,8 @@ TEST(ensure_edge)
{
srand (0);
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
double res = 128.0;
db::DEdge ee[] = {
@ -451,7 +518,7 @@ TEST(ensure_edge)
for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) {
clip_box += ee[i].p1 ();
}
for (auto t = tris.begin (); t != tris.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
if (clip_box.overlaps (t->bbox ())) {
EXPECT_EQ (t->bbox ().inside (clip_box), true);
area_in += t->area ();
@ -461,7 +528,7 @@ TEST(ensure_edge)
EXPECT_EQ (tl::to_string (area_in), "0.25");
}
bool safe_inside (const db::DBox &b1, const db::DBox &b2)
static bool safe_inside (const db::DBox &b1, const db::DBox &b2)
{
typedef db::coord_traits<db::DBox::coord_type> ct;
@ -475,7 +542,8 @@ TEST(constrain)
{
srand (0);
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
double res = 128.0;
db::DEdge ee[] = {
@ -500,11 +568,11 @@ TEST(constrain)
}
}
std::vector<db::Vertex *> contour;
std::vector<db::plc::Vertex *> contour;
for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) {
contour.push_back (tris.insert_point (ee[i].p1 ()));
}
std::vector<std::vector<db::Vertex *> > contours;
std::vector<std::vector<db::plc::Vertex *> > contours;
contours.push_back (contour);
EXPECT_EQ (tris.check (), true);
@ -521,7 +589,7 @@ TEST(constrain)
for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) {
clip_box += ee[i].p1 ();
}
for (auto t = tris.begin (); t != tris.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_EQ (clip_box.overlaps (t->bbox ()), true);
EXPECT_EQ (safe_inside (t->bbox (), clip_box), true);
area_in += t->area ();
@ -539,7 +607,8 @@ TEST(heavy_constrain)
srand (l);
tl::info << "." << tl::noendl;
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
double res = 128.0;
db::DEdge ee[] = {
@ -566,11 +635,11 @@ TEST(heavy_constrain)
}
}
std::vector<db::Vertex *> contour;
std::vector<db::plc::Vertex *> contour;
for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) {
contour.push_back (tris.insert_point (ee[i].p1 ()));
}
std::vector<std::vector<db::Vertex *> > contours;
std::vector<std::vector<db::plc::Vertex *> > contours;
contours.push_back (contour);
EXPECT_EQ (tris.check (), true);
@ -587,7 +656,7 @@ TEST(heavy_constrain)
for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) {
clip_box += ee[i].p1 ();
}
for (auto t = tris.begin (); t != tris.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_EQ (clip_box.overlaps (t->bbox ()), true);
EXPECT_EQ (safe_inside (t->bbox (), clip_box), true);
area_in += t->area ();
@ -609,12 +678,13 @@ TEST(heavy_find_point_around)
srand (l);
tl::info << "." << tl::noendl;
TestableTriangles tris;
db::plc::Graph plc;
TestableTriangulation tris (&plc);
double res = 128.0;
unsigned int n = rand () % 190 + 10;
std::vector<db::Vertex *> vertexes;
std::vector<db::plc::Vertex *> vertexes;
for (unsigned int i = 0; i < n; ++i) {
double x = round (flt_rand () * res) * (1.0 / res);
@ -633,8 +703,8 @@ TEST(heavy_find_point_around)
auto p1 = tris.find_points_around (vertex, r);
auto p2 = tris.find_inside_circle (*vertex, r);
std::set<db::Vertex *> sp1 (p1.begin (), p1.end ());
std::set<db::Vertex *> sp2 (p2.begin (), p2.end ());
std::set<db::plc::Vertex *> sp1 (p1.begin (), p1.end ());
std::set<db::plc::Vertex *> sp2 (p2.begin (), p2.end ());
sp2.erase (vertex);
EXPECT_EQ (sp1 == sp2, true);
@ -656,13 +726,14 @@ TEST(create_constrained_delaunay)
r -= r2;
TestableTriangles tri;
tri.create_constrained_delaunay (r);
tri.remove_outside_triangles ();
db::plc::Graph plc;
TestableTriangulation tris (&plc);
tris.create_constrained_delaunay (r);
tris.remove_outside_triangles ();
EXPECT_EQ (tri.check (), true);
EXPECT_EQ (tris.check (), true);
EXPECT_EQ (tri.to_string (),
EXPECT_EQ (plc.to_string (),
"((1000, 0), (0, 0), (200, 200)), "
"((0, 1000), (200, 200), (0, 0)), "
"((1000, 0), (200, 200), (800, 200)), "
@ -683,22 +754,23 @@ TEST(triangulate_basic)
r -= r2;
db::Triangles::TriangulateParameters param;
db::plc::TriangulationParameters param;
param.min_b = 1.2;
param.max_area = 1.0;
TestableTriangles tri;
db::plc::Graph plc;
TestableTriangulation tri (&plc);
tri.triangulate (r, param, 0.001);
EXPECT_EQ (tri.check (), true);
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_LE (t->area (), param.max_area);
EXPECT_GE (t->b (), param.min_b);
}
EXPECT_GT (tri.num_triangles (), size_t (100));
EXPECT_LT (tri.num_triangles (), size_t (150));
EXPECT_GT (plc.num_polygons (), size_t (100));
EXPECT_LT (plc.num_polygons (), size_t (150));
// for debugging:
// tri.dump ("debug.gds");
@ -710,16 +782,16 @@ TEST(triangulate_basic)
EXPECT_EQ (tri.check (), true);
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_LE (t->area (), param.max_area);
EXPECT_GE (t->b (), param.min_b);
}
EXPECT_GT (tri.num_triangles (), size_t (900));
EXPECT_LT (tri.num_triangles (), size_t (1000));
EXPECT_GT (plc.num_polygons (), size_t (900));
EXPECT_LT (plc.num_polygons (), size_t (1000));
}
void read_polygons (const std::string &path, db::Region &region, double dbu)
static void read_polygons (const std::string &path, db::Region &region, double dbu)
{
tl::InputStream is (path);
tl::TextInputStream ti (is);
@ -801,12 +873,13 @@ TEST(triangulate_geo)
}
db::Triangles::TriangulateParameters param;
db::plc::TriangulationParameters param;
param.min_b = 1.0;
param.max_area = 0.1;
param.min_length = 0.001;
TestableTriangles tri;
db::plc::Graph plc;
TestableTriangulation tri (&plc);
tri.triangulate (r, param, dbu);
EXPECT_EQ (tri.check (false), true);
@ -815,7 +888,7 @@ TEST(triangulate_geo)
// tri.dump ("debug.gds");
size_t n_skinny = 0;
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_LE (t->area (), param.max_area);
if (t->b () < param.min_b) {
++n_skinny;
@ -823,8 +896,8 @@ TEST(triangulate_geo)
}
EXPECT_LT (n_skinny, size_t (20));
EXPECT_GT (tri.num_triangles (), size_t (29000));
EXPECT_LT (tri.num_triangles (), size_t (30000));
EXPECT_GT (plc.num_polygons (), size_t (29000));
EXPECT_LT (plc.num_polygons (), size_t (30000));
}
TEST(triangulate_analytic)
@ -860,11 +933,12 @@ TEST(triangulate_analytic)
rg = db::Region (sp1) - db::Region (sp2);
db::Triangles::TriangulateParameters param;
db::plc::TriangulationParameters param;
param.min_b = 1.0;
param.max_area = 0.01;
TestableTriangles tri;
db::plc::Graph plc;
TestableTriangulation tri (&plc);
tri.triangulate (rg, param, dbu);
EXPECT_EQ (tri.check (false), true);
@ -872,13 +946,13 @@ TEST(triangulate_analytic)
// for debugging:
// tri.dump ("debug.gds");
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_LE (t->area (), param.max_area);
EXPECT_GE (t->b (), param.min_b);
}
EXPECT_GT (tri.num_triangles (), size_t (1250));
EXPECT_LT (tri.num_triangles (), size_t (1300));
EXPECT_GT (plc.num_polygons (), size_t (1250));
EXPECT_LT (plc.num_polygons (), size_t (1300));
}
TEST(triangulate_problematic)
@ -899,12 +973,13 @@ TEST(triangulate_problematic)
db::DPolygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
db::Triangles::TriangulateParameters param;
db::plc::TriangulationParameters param;
param.min_b = 1.0;
param.max_area = 100000.0;
param.min_length = 0.002;
TestableTriangles tri;
db::plc::Graph plc;
TestableTriangulation tri (&plc);
tri.triangulate (poly, param);
EXPECT_EQ (tri.check (false), true);
@ -912,13 +987,13 @@ TEST(triangulate_problematic)
// for debugging:
// tri.dump ("debug.gds");
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_LE (t->area (), param.max_area);
EXPECT_GE (t->b (), param.min_b);
}
EXPECT_GT (tri.num_triangles (), size_t (540));
EXPECT_LT (tri.num_triangles (), size_t (560));
EXPECT_GT (plc.num_polygons (), size_t (540));
EXPECT_LT (plc.num_polygons (), size_t (560));
}
TEST(triangulate_thin)
@ -943,12 +1018,13 @@ TEST(triangulate_thin)
double dbu = 0.001;
db::Triangles::TriangulateParameters param;
db::plc::TriangulationParameters param;
param.min_b = 0.5;
param.max_area = 0.0;
param.min_length = 2 * dbu;
TestableTriangles tri;
db::plc::Graph plc;
TestableTriangulation tri (&plc);
db::DCplxTrans trans = db::DCplxTrans (dbu) * db::DCplxTrans (db::DTrans (db::DPoint () - poly.box ().center ()));
tri.triangulate (trans * poly, param);
@ -957,12 +1033,12 @@ TEST(triangulate_thin)
// for debugging:
// tri.dump ("debug.gds");
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_GE (t->b (), param.min_b);
}
EXPECT_GT (tri.num_triangles (), size_t (13000));
EXPECT_LT (tri.num_triangles (), size_t (13200));
EXPECT_GT (plc.num_polygons (), size_t (13000));
EXPECT_LT (plc.num_polygons (), size_t (13200));
}
TEST(triangulate_issue1996)
@ -979,11 +1055,12 @@ TEST(triangulate_issue1996)
double dbu = 0.001;
db::Triangles::TriangulateParameters param;
db::plc::TriangulationParameters param;
param.min_b = 0.5;
param.max_area = 5000.0 * dbu * dbu;
TestableTriangles tri;
db::plc::Graph plc;
TestableTriangulation tri (&plc);
db::DCplxTrans trans = db::DCplxTrans (dbu) * db::DCplxTrans (db::DTrans (db::DPoint () - poly.box ().center ()));
tri.triangulate (trans * poly, param);
@ -992,13 +1069,13 @@ TEST(triangulate_issue1996)
// for debugging:
// tri.dump ("debug.gds");
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_LE (t->area (), param.max_area);
EXPECT_GE (t->b (), param.min_b);
}
EXPECT_GT (tri.num_triangles (), size_t (128000));
EXPECT_LT (tri.num_triangles (), size_t (132000));
EXPECT_GT (plc.num_polygons (), size_t (128000));
EXPECT_LT (plc.num_polygons (), size_t (132000));
}
TEST(triangulate_with_vertexes)
@ -1015,17 +1092,18 @@ TEST(triangulate_with_vertexes)
double dbu = 0.001;
db::Triangles::TriangulateParameters param;
db::plc::TriangulationParameters param;
param.min_b = 0.0;
param.max_area = 0.0;
std::vector<db::Point> vertexes;
TestableTriangles tri;
db::plc::Graph plc;
TestableTriangulation tri (&plc);
db::CplxTrans trans = db::DCplxTrans (dbu) * db::CplxTrans (db::Trans (db::Point () - poly.box ().center ()));
tri.triangulate (poly, param, trans);
EXPECT_EQ (tri.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (0.5, 0.05)), ((0.5, -0.05), (-0.5, -0.05), (0.5, 0.05))");
EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (0.5, 0.05)), ((0.5, -0.05), (-0.5, -0.05), (0.5, 0.05))");
vertexes.clear ();
@ -1033,18 +1111,36 @@ TEST(triangulate_with_vertexes)
vertexes.push_back (db::Point (50, 150));
tri.triangulate (poly, vertexes, param, trans);
EXPECT_EQ (tri.to_string (), "((-0.5, -0.05), (-0.133333333333, 0.05), (0.5, -0.05)), ((0.5, 0.05), (0.5, -0.05), (-0.133333333333, 0.05)), ((-0.133333333333, 0.05), (-0.5, -0.05), (-0.5, 0.05))");
EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.133333333333, 0.05), (0.5, -0.05)), ((0.5, 0.05), (0.5, -0.05), (-0.133333333333, 0.05)), ((-0.133333333333, 0.05), (-0.5, -0.05), (-0.5, 0.05))");
for (auto v = vertexes.begin (); v != vertexes.end (); ++v) {
auto *vp = tri.find_vertex_for_point (trans * *v);
EXPECT_EQ (vp, 0);
}
// normal triangulation
vertexes.clear ();
vertexes.push_back (db::Point (50, 50));
tri.triangulate (poly, vertexes, param, trans);
EXPECT_EQ (tri.to_string (), "((-0.45, 0), (-0.5, -0.05), (-0.5, 0.05)), ((0.5, 0.05), (-0.45, 0), (-0.5, 0.05)), ((-0.45, 0), (0.5, -0.05), (-0.5, -0.05)), ((-0.45, 0), (0.5, 0.05), (0.5, -0.05))");
EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (-0.45, 0)), ((-0.5, 0.05), (0.5, 0.05), (-0.45, 0)), ((0.5, -0.05), (-0.45, 0), (0.5, 0.05)), ((0.5, -0.05), (-0.5, -0.05), (-0.45, 0))");
for (auto v = vertexes.begin (); v != vertexes.end (); ++v) {
auto *vp = tri.find_vertex_for_point (trans * *v);
if (! vp) {
tl::warn << "Vertex not present in output: " << v->to_string ();
EXPECT_EQ (1, 0);
}
}
// linear chain of vertexes must not break triangulation
vertexes.clear ();
vertexes.push_back (db::Point (50, 50));
vertexes.push_back (db::Point (100, 50));
vertexes.push_back (db::Point (150, 50));
tri.triangulate (poly, vertexes, param, trans);
EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (-0.45, 0)), ((-0.4, 0), (-0.45, 0), (-0.5, 0.05)), ((-0.5, -0.05), (-0.45, 0), (-0.4, 0)), ((0.5, -0.05), (-0.35, 0), (0.5, 0.05)), ((-0.5, -0.05), (-0.35, 0), (0.5, -0.05)), ((-0.5, -0.05), (-0.4, 0), (-0.35, 0)), ((-0.35, 0), (-0.5, 0.05), (0.5, 0.05)), ((-0.35, 0), (-0.4, 0), (-0.5, 0.05))");
for (auto v = vertexes.begin (); v != vertexes.end (); ++v) {
auto *vp = tri.find_vertex_for_point (trans * *v);
@ -1060,10 +1156,10 @@ TEST(triangulate_with_vertexes)
tri.triangulate (poly, vertexes, param, trans);
EXPECT_GT (tri.num_triangles (), size_t (380));
EXPECT_LT (tri.num_triangles (), size_t (400));
EXPECT_GT (plc.num_polygons (), size_t (380));
EXPECT_LT (plc.num_polygons (), size_t (420));
for (auto t = tri.begin (); t != tri.end (); ++t) {
for (auto t = plc.begin (); t != plc.end (); ++t) {
EXPECT_LE (t->area (), param.max_area);
EXPECT_GE (t->b (), param.min_b);
}

View File

@ -12,13 +12,15 @@ SOURCES = \
dbFillToolTests.cc \
dbLogTests.cc \
dbObjectWithPropertiesTests.cc \
dbPLCConvexDecompositionTests.cc \
dbPLCGraphTests.cc \
dbPLCTests.cc \
dbPLCTriangulationTests.cc \
dbPolygonNeighborhoodTests.cc \
dbPropertiesFilterTests.cc \
dbQuadTreeTests.cc \
dbRecursiveInstanceIteratorTests.cc \
dbRegionCheckUtilsTests.cc \
dbTriangleTests.cc \
dbTrianglesTests.cc \
dbUtilsTests.cc \
dbWriterTools.cc \
dbLoadLayoutOptionsTests.cc \

View File

@ -218,7 +218,7 @@ A::ia_cref_to_qs (const std::vector<int> &ia)
{
QString s;
for (std::vector<int>::const_iterator i = ia.begin (); i != ia.end (); ++i) {
s.push_back (char (*i));
s.push_back (QChar (*i));
}
return s;
}
@ -229,7 +229,7 @@ A::ia_cref_to_qs_ref (const std::vector<int> &ia)
static QString s;
s.clear ();
for (std::vector<int>::const_iterator i = ia.begin (); i != ia.end (); ++i) {
s.push_back (char (*i));
s.push_back (QChar (*i));
}
return s;
}
@ -243,7 +243,7 @@ A::ql1s_cref_to_ia (const QLatin1String &ql1s)
const char *cp = ql1s.data ();
size_t n = ql1s.size ();
for (size_t i = 0; i < n; ++i) {
ia.push_back (*cp++);
ia.push_back ((unsigned char) *cp++);
}
return ia;
}
@ -1270,7 +1270,15 @@ static gsi::Class<A> decl_a ("", "A",
gsi::method ("to_s", &A::to_s) +
gsi::iterator ("a6", &A::a6b, &A::a6e) +
gsi::iterator ("a7", &A::a7b, &A::a7e) +
gsi::iterator ("a8", &A::a8b, &A::a8e)
gsi::iterator ("a8", &A::a8b, &A::a8e) +
#if defined(HAVE_QT)
gsi::method ("ft_qba", &A::ft_qba) +
gsi::method ("ft_qs", &A::ft_qs) +
#endif
gsi::method ("ft_str", &A::ft_str) +
gsi::method ("ft_cv", &A::ft_cv) +
gsi::method ("ft_cptr", &A::ft_cptr) +
gsi::method ("ft_var", &A::ft_var)
);
static gsi::Class<A_NC> decl_a_nc (decl_a, "", "A_NC");

View File

@ -419,6 +419,17 @@ struct A
static int sp_i_get ();
static void sp_i_set (int v);
// feed-through values for full cycle tests
// (mainly for string encoding and binary strings)
static std::string ft_str (const std::string &v) { return v; }
static std::vector<char> ft_cv (const std::vector<char> &v) { return v; }
static const char *ft_cptr (const char *v) { return v; }
static tl::Variant ft_var (const tl::Variant &v) { return v; }
#if defined(HAVE_QT)
static QString ft_qs (const QString &v) { return v; }
static QByteArray ft_qba (const QByteArray &v) { return v; }
#endif
// members
std::vector<double> m_d;
int n;

View File

@ -1,6 +1,7 @@
TL_INC = $$PWD/tl/tl
DB_INC = $$PWD/db/db
PEX_INC = $$PWD/pex/pex
DRC_INC = $$PWD/drc/drc
LVS_INC = $$PWD/lvs/lvs
EDT_INC = $$PWD/edt/edt

View File

@ -7,6 +7,7 @@ SUBDIRS = \
tl \
gsi \
db \
pex \
rdb \
lib \
plugins \
@ -62,12 +63,13 @@ equals(HAVE_PYTHON, "1") {
gsi.depends += tl
db.depends += gsi
pex.depends += db
rdb.depends += db
lib.depends += db
lym.depends += gsi $$LANG_DEPENDS
laybasic.depends += rdb
laybasic.depends += rdb pex
layview.depends += laybasic
ant.depends += layview
@ -115,12 +117,12 @@ equals(HAVE_RUBY, "1") {
} else {
plugins.depends += layview ant img edt
plugins.depends += layview ant img edt
}
buddies.depends += plugins lym $$LANG_DEPENDS
unit_tests.depends += plugins lym $$MAIN_DEPENDS $$LANG_DEPENDS
buddies.depends += plugins pex lym $$LANG_DEPENDS
unit_tests.depends += plugins pex lym $$MAIN_DEPENDS $$LANG_DEPENDS
!equals(HAVE_QT, "0") {

View File

@ -38,8 +38,9 @@
#include "version.h"
// required to force linking of the "ext" and "lib" module
// required to force linking of the "lib" and other modules
#include "libForceLink.h"
#include "pexForceLink.h"
#include "antForceLink.h"
#include "imgForceLink.h"
#include "docForceLink.h"

6
src/pex/pex.pro Normal file
View File

@ -0,0 +1,6 @@
TEMPLATE = subdirs
SUBDIRS = pex unit_tests
unit_tests.depends += pex

View File

@ -0,0 +1,124 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "gsiDecl.h"
#include "pexRExtractor.h"
#include "pexSquareCountingRExtractor.h"
#include "pexTriangulationRExtractor.h"
#include "gsiEnums.h"
namespace gsi
{
static pex::RExtractor *new_sqc_rextractor (double dbu, bool skip_simplify)
{
auto res = new pex::SquareCountingRExtractor (dbu);
res->set_skip_simplfy (skip_simplify);
return res;
}
static pex::RExtractor *new_tesselation_rextractor (double dbu, double min_b, double max_area, bool skip_reduction)
{
auto res = new pex::TriangulationRExtractor (dbu);
res->triangulation_parameters ().min_b = min_b;
res->triangulation_parameters ().max_area = max_area;
res->set_skip_reduction (skip_reduction);
return res;
}
static pex::RNetwork *extract_ipolygon (pex::RExtractor *rex, const db::Polygon &poly, const std::vector<db::Point> &vertex_ports, const std::vector<db::Polygon> &polygon_ports)
{
std::unique_ptr<pex::RNetwork> p_network (new pex::RNetwork ());
rex->extract (poly, vertex_ports, polygon_ports, *p_network);
return p_network.release ();
}
Class<pex::RExtractor> decl_RExtractor ("pex", "RExtractor",
gsi::constructor ("square_counting_extractor", &new_sqc_rextractor, gsi::arg ("dbu"), gsi::arg ("skip_simplify", false),
"@brief Creates a square counting R extractor\n"
"The square counting extractor extracts resistances from a polygon with ports using the following approach:\n"
"\n"
"@ul\n"
"@li Split the original polygon into convex parts using a Hertel-Mehlhorn decomposition @/li\n"
"@li Create internal nodes at the locations where the parts touch @/li\n"
"@li For each part, extract the resistance along the horizonal or vertical axis, whichever is longer @/li"
"@/ul\n"
"\n"
"The square counting extractor assumes the parts are 'thin' - i.e. the long axis is much longer than the short "
"axis - and the parts are either oriented horizontally or vertically. The current flow is assumed to be linear and "
"homogenous along the long axis. Ports define probe points for the voltages along the long long axis. "
"Polygon ports are considered points located at the center of the polygon's bounding box.\n"
"\n"
"The results of the extraction is normalized to a sheet resistance of 1 Ohm/square - i.e. to obtain the actual resistor "
"values, multiply the element resistance values by the sheet resistance.\n"
"\n"
"@param dbu The database unit of the polygons the extractor will work on\n"
"@param skip_simplify If true, the final step to simplify the netlist will be skipped. This feature is for testing mainly.\n"
"@return A new \\RExtractor object that implements the square counting extractor\n"
) +
gsi::constructor ("tesselation_extractor", &new_tesselation_rextractor, gsi::arg ("dbu"), gsi::arg ("min_b", 0.3), gsi::arg ("max_area", 0.0), gsi::arg ("skip_reduction", false),
"@brief Creates a tesselation R extractor\n"
"The tesselation extractor starts with a triangulation of the original polygon. The triangulation is "
"turned into a resistor network and simplified.\n"
"\n"
"The tesselation extractor is well suited for homogeneous geometries, but does not properly consider "
"the boundary conditions at the borders of the region. It is good for extracting resistance networks of "
"substrate or large sheet layers.\n"
"\n"
"The square counting extractor assumes the parts are 'thin' - i.e. the long axis is much longer than the short "
"axis - and the parts are either oriented horizontally or vertically. The current flow is assumed to be linear and "
"homogenous along the long axis. Ports define probe points for the voltages along the long long axis. "
"Polygon ports are considered points located at the center of the polygon's bounding box.\n"
"\n"
"The tesselation extractor delivers a full matrix of resistors - there is a resistor between every pair of ports.\n"
"\n"
"The results of the extraction is normalized to a sheet resistance of 1 Ohm/square - i.e. to obtain the actual resistor "
"values, multiply the element resistance values by the sheet resistance.\n"
"\n"
"@param dbu The database unit of the polygons the extractor will work on\n"
"@param min_b Defines the min 'b' value of the refined Delaunay triangulation (see \\Polygon#delaunay)\n"
"@param max_area Defines maximum area value of the refined Delaunay triangulation (see \\Polygon#delaunay). The value is given in square micrometer units.\n"
"@param skip_reduction If true, the reduction step for the netlist will be skipped. This feature is for testing mainly. The resulting R graph will contain all the original triangles and the internal nodes representing the vertexes.\n"
"@return A new \\RExtractor object that implements the square counting extractor\n"
) +
gsi::factory_ext ("extract", &extract_ipolygon, gsi::arg ("polygon"), gsi::arg ("vertex_ports", std::vector<db::Point> (), "[]"), gsi::arg ("polygon_ports", std::vector<db::Polygon> (), "[]"),
"@brief Runs the extraction on the given polygon\n"
"This method will create a new \\RNetwork object from the given polygon.\n"
"\n"
"'vertex_ports' is an array of points that define point-like ports. A port will create a \\RNode object in the "
"resistor graph. This node object carries the type \\VertexPort and the index of the vertex in this array.\n"
"\n"
"'polygon_ports' is an array of polygons that define distributed ports. The polygons should be inside the resistor polygon "
"and convex. A port will create a \\RNode object in the resistor graph. "
"For polygon ports, this node object carries the type \\PolygonPort and the index of the polygon in this array.\n"
),
"@brief The basic R extractor class\n"
"\n"
"Use \\tesselation_extractor and \\square_counting_extractor to create an actual extractor object.\n"
"To use the extractor, call the \\extract method on a given polygon with ports that define the network attachment points.\n"
"\n"
"This class has been introduced in version 0.30.2\n"
);
}

View File

@ -0,0 +1,402 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "gsiDecl.h"
#include "pexRExtractorTech.h"
#include "pexRNetExtractor.h"
#include "pexRNetwork.h"
#include "gsiEnums.h"
namespace gsi
{
static unsigned int via_get_bottom_conductor (const pex::RExtractorTechVia *via)
{
return via->bottom_conductor;
}
static void via_set_bottom_conductor (pex::RExtractorTechVia *via, unsigned int l)
{
via->bottom_conductor = l;
}
static unsigned int via_get_cut_layer (const pex::RExtractorTechVia *via)
{
return via->cut_layer;
}
static void via_set_cut_layer (pex::RExtractorTechVia *via, unsigned int l)
{
via->cut_layer = l;
}
static unsigned int via_get_top_conductor (const pex::RExtractorTechVia *via)
{
return via->top_conductor;
}
static void via_set_top_conductor (pex::RExtractorTechVia *via, unsigned int l)
{
via->top_conductor = l;
}
static double via_get_resistance (const pex::RExtractorTechVia *via)
{
return via->resistance;
}
static void via_set_resistance (pex::RExtractorTechVia *via, double r)
{
via->resistance = r;
}
static double via_get_merge_distance (const pex::RExtractorTechVia *via)
{
return via->merge_distance;
}
static void via_set_merge_distance (pex::RExtractorTechVia *via, double dist)
{
via->merge_distance = dist;
}
Class<pex::RExtractorTechVia> decl_RExtractorTechVia ("pex", "RExtractorTechVia",
gsi::method ("to_s", &pex::RExtractorTechVia::to_string,
"@brief Returns a string describing this object"
) +
gsi::method_ext ("merge_distance", &via_get_merge_distance,
"@brief Gets the merge distance\n"
"If this value is not zero, it specifies the distance below (or equal) which "
"vias are merged into bigger blocks. This is an optimization to reduce the "
"complexity of the via extraction. The value is given in micrometers."
) +
gsi::method_ext ("merge_distance=", &via_set_merge_distance, gsi::arg ("d"),
"@brief Sets the merge distance\n"
"See \\merge_distance for a description of this attribute."
) +
gsi::method_ext ("resistance", &via_get_resistance,
"@brief Gets the area resistance value of the vias\n"
"This value specifies the via resistance in Ohm * square micrometers. "
"The actual resistance is obtained by dividing this value by the via area."
) +
gsi::method_ext ("resistance=", &via_set_resistance, gsi::arg ("d"),
"@brief Sets the via area resistance value\n"
"See \\resistance for a description of this attribute."
) +
gsi::method_ext ("bottom_conductor", &via_get_bottom_conductor,
"@brief Gets the bottom conductor layer index\n"
"The layer index is a generic identifier for the layer. It is the value used as key in the "
"geometry and port arguments of \\RNetExtractor#extract."
) +
gsi::method_ext ("bottom_conductor=", &via_set_bottom_conductor, gsi::arg ("l"),
"@brief Sets the via bottom conductor layer index\n"
"See \\bottom_conductor for a description of this attribute."
) +
gsi::method_ext ("cut_layer", &via_get_cut_layer,
"@brief Gets the cut layer index\n"
"The layer index is a generic identifier for the layer. It is the value used as key in the "
"geometry and port arguments of \\RNetExtractor#extract. "
"The cut layer is the layer where the via exists."
) +
gsi::method_ext ("cut_layer=", &via_set_cut_layer, gsi::arg ("l"),
"@brief Sets the cut layer index\n"
"See \\cut_layer for a description of this attribute."
) +
gsi::method_ext ("top_conductor", &via_get_top_conductor,
"@brief Gets the top conductor layer index\n"
"The layer index is a generic identifier for the layer. It is the value used as key in the "
"geometry and port arguments of \\RNetExtractor#extract."
) +
gsi::method_ext ("top_conductor=", &via_set_top_conductor, gsi::arg ("l"),
"@brief Sets the via top conductor layer index\n"
"See \\top_conductor for a description of this attribute."
),
"@brief Describes a via for the network extraction.\n"
"This class is used to describe a via type in the context of "
"the \\RExtractorTech#extract method.\n"
"\n"
"This class has been introduced in version 0.30.2."
);
static pex::RExtractorTechConductor::Algorithm cond_get_algorithm (const pex::RExtractorTechConductor *cond)
{
return cond->algorithm;
}
static void cond_set_algorithm (pex::RExtractorTechConductor *cond, pex::RExtractorTechConductor::Algorithm a)
{
cond->algorithm = a;
}
static unsigned int cond_get_layer (const pex::RExtractorTechConductor *cond)
{
return cond->layer;
}
static void cond_set_layer (pex::RExtractorTechConductor *cond, unsigned int l)
{
cond->layer = l;
}
static double cond_get_resistance (const pex::RExtractorTechConductor *cond)
{
return cond->resistance;
}
static void cond_set_resistance (pex::RExtractorTechConductor *cond, double r)
{
cond->resistance = r;
}
static double cond_get_triangulation_min_b (const pex::RExtractorTechConductor *cond)
{
return cond->triangulation_min_b;
}
static void cond_set_triangulation_min_b (pex::RExtractorTechConductor *cond, double min_b)
{
cond->triangulation_min_b = min_b;
}
static double cond_get_triangulation_max_area (const pex::RExtractorTechConductor *cond)
{
return cond->triangulation_max_area;
}
static void cond_set_triangulation_max_area (pex::RExtractorTechConductor *cond, double max_area)
{
cond->triangulation_max_area = max_area;
}
Class<pex::RExtractorTechConductor> decl_RExtractorTechConductor ("pex", "RExtractorTechConductor",
gsi::method ("to_s", &pex::RExtractorTechConductor::to_string,
"@brief Returns a string describing this object"
) +
gsi::method_ext ("algorithm", &cond_get_algorithm,
"@brief Gets the algorithm to use\n"
"Specifies the algorithm to use. The default algorithm is 'SquareCounting'."
) +
gsi::method_ext ("algorithm=", &cond_set_algorithm, gsi::arg ("d"),
"@brief Sets the algorithm to use\n"
"See \\algorithm for a description of this attribute."
) +
gsi::method_ext ("resistance", &cond_get_resistance,
"@brief Gets the sheet resistance value of the conductor layer\n"
"This value specifies the cond resistance in Ohm per square. "
"The actual resistance is obtained by multiplying this value with the number of squares."
) +
gsi::method_ext ("resistance=", &cond_set_resistance, gsi::arg ("r"),
"@brief Sets the sheet resistance value of the conductor layer\n"
"See \\resistance for a description of this attribute."
) +
gsi::method_ext ("layer", &cond_get_layer,
"@brief Gets the layer index\n"
"The layer index is a generic identifier for the layer. It is the value used as key in the "
"geometry and port arguments of \\RNetExtractor#extract. "
"This attribute specifies the layer the conductor is on."
) +
gsi::method_ext ("layer=", &cond_set_layer, gsi::arg ("l"),
"@brief Sets the layer index\n"
"See \\layer for a description of this attribute."
) +
gsi::method_ext ("triangulation_min_b", &cond_get_triangulation_min_b,
"@brief Gets the triangulation 'min_b' parameter\n"
"This parameter is used for the 'Tesselation' algorithm and specifies the shortest edge to circle radius ratio of "
"the Delaunay triangulation. "
) +
gsi::method_ext ("triangulation_min_b=", &cond_set_triangulation_min_b, gsi::arg ("min_b"),
"@brief Sets the triangulation 'min_b' parameter\n"
"See \\triangulation_min_b for a description of this attribute."
) +
gsi::method_ext ("triangulation_max_area", &cond_get_triangulation_max_area,
"@brief Gets the triangulation 'max_area' parameter\n"
"This parameter is used for the 'Tesselation' algorithm and specifies the maximum area of "
"the triangles in square micrometers."
) +
gsi::method_ext ("triangulation_max_area=", &cond_set_triangulation_max_area, gsi::arg ("max_area"),
"@brief Sets the triangulation 'max_area' parameter\n"
"See \\triangulation_max_area for a description of this attribute."
),
"@brief Describes a conductor layer for the network extraction.\n"
"This class is used to describe a conductor layer in the context of "
"the \\RExtractorTech#extract method.\n"
"\n"
"This class has been introduced in version 0.30.2."
);
gsi::Enum<pex::RExtractorTechConductor::Algorithm> decl_RExtractorTechConductor_Algorithm ("pex", "Algorithm",
gsi::enum_const ("SquareCounting", pex::RExtractorTechConductor::SquareCounting,
"@brief Specifies the square counting algorithm for \\RExtractorTechConductor#algorithm.\n"
"See \\RExtractor#square_counting_extractor for more details."
) +
gsi::enum_const ("Tesselation", pex::RExtractorTechConductor::Tesselation,
"@brief Specifies the square counting algorithm for \\RExtractorTechConductor#algorithm.\n"
"See \\RExtractor#tesselation_extractor for more details."
),
"@brief This enum represents the extraction algorithm for \\RExtractorTechConductor.\n"
"\n"
"This enum has been introduced in version 0.30.2."
);
gsi::ClassExt<pex::RExtractorTechConductor> inject_RExtractorTechConductor_in_parent (decl_RExtractorTechConductor_Algorithm.defs ());
static bool tech_get_skip_simplify (const pex::RExtractorTech *tech)
{
return tech->skip_simplify;
}
static void tech_set_skip_simplify (pex::RExtractorTech *tech, bool f)
{
tech->skip_simplify = f;
}
static std::list<pex::RExtractorTechVia>::const_iterator tech_begin_vias (const pex::RExtractorTech *tech)
{
return tech->vias.begin ();
}
static std::list<pex::RExtractorTechVia>::const_iterator tech_end_vias (const pex::RExtractorTech *tech)
{
return tech->vias.end ();
}
static void tech_clear_vias (pex::RExtractorTech *tech)
{
tech->vias.clear ();
}
static void tech_add_via (pex::RExtractorTech *tech, const pex::RExtractorTechVia &via)
{
tech->vias.push_back (via);
}
static std::list<pex::RExtractorTechConductor>::const_iterator tech_begin_conductors (const pex::RExtractorTech *tech)
{
return tech->conductors.begin ();
}
static std::list<pex::RExtractorTechConductor>::const_iterator tech_end_conductors (const pex::RExtractorTech *tech)
{
return tech->conductors.end ();
}
static void tech_clear_conductors (pex::RExtractorTech *tech)
{
tech->conductors.clear ();
}
static void tech_add_conductor (pex::RExtractorTech *tech, const pex::RExtractorTechConductor &conductor)
{
tech->conductors.push_back (conductor);
}
Class<pex::RExtractorTech> decl_RExtractorTech ("pex", "RExtractorTech",
gsi::method ("to_s", &pex::RExtractorTech::to_string,
"@brief Returns a string describing this object"
) +
gsi::method_ext ("skip_simplify", &tech_get_skip_simplify,
"@brief Gets a value indicating whether to skip the simplify step\n"
"This values specifies to skip the simplify step of the network after the extraction has "
"been done. By default, the network is simplified - i.e. serial resistors are joined etc. "
"By setting this attribute to 'false', this step is skipped."
) +
gsi::method_ext ("skip_simplify=", &tech_set_skip_simplify, gsi::arg ("f"),
"@brief Sets a value indicating whether to skip the simplify step\n"
"See \\skip_simplify for a description of this attribute."
) +
gsi::iterator_ext ("each_via", &tech_begin_vias, &tech_end_vias,
"@brief Iterates the list of via definitions\n"
) +
gsi::method_ext ("clear_vias", &tech_clear_vias,
"@brief Clears the list of via definitions\n"
) +
gsi::method_ext ("add_via", &tech_add_via, gsi::arg ("via"),
"@brief Adds the given via definition to the list of vias\n"
) +
gsi::iterator_ext ("each_conductor", &tech_begin_conductors, &tech_end_conductors,
"@brief Iterates the list of conductor definitions\n"
) +
gsi::method_ext ("clear_conductors", &tech_clear_conductors,
"@brief Clears the list of conductor definitions\n"
) +
gsi::method_ext ("add_conductor", &tech_add_conductor, gsi::arg ("conductor"),
"@brief Adds the given conductor definition to the list of conductors\n"
),
"@brief Specifies the tech stack for the R extraction.\n"
"The tech stack is a collection of via and conductor definitions and some other attributes. "
"It is used for the \\RNetExtractor#extract method.\n"
"\n"
"This enum has been introduced in version 0.30.2."
);
static pex::RNetExtractor *new_net_rextractor (double dbu)
{
return new pex::RNetExtractor (dbu);
}
static pex::RNetwork *rex_extract (pex::RNetExtractor *rex,
const pex::RExtractorTech *tech,
const std::map<unsigned int, db::Region> *geo,
const std::map<unsigned int, std::vector<db::Point> > *vertex_ports,
const std::map<unsigned int, std::vector<db::Polygon> > *polygon_ports)
{
std::unique_ptr<pex::RNetwork> network (new pex::RNetwork ());
std::map<unsigned int, db::Region> empty_geo;
std::map<unsigned int, std::vector<db::Point> > empty_vertex_ports;
std::map<unsigned int, std::vector<db::Polygon> > empty_polygon_ports;
rex->extract (*tech, geo ? *geo : empty_geo, vertex_ports ? *vertex_ports : empty_vertex_ports, polygon_ports ? *polygon_ports : empty_polygon_ports, *network);
return network.release ();
}
Class<pex::RNetExtractor> decl_RNetExtractor ("pex", "RNetExtractor",
gsi::constructor ("new", &new_net_rextractor, gsi::arg ("dbu"),
"@brief Creates a network R extractor\n"
"\n"
"@param dbu The database unit of the polygons the extractor will work on\n"
"@param skip_simplify If true, the final step to simplify the netlist will be skipped. This feature is for testing mainly.\n"
"@return A new \\RNetExtractor object that implements the net extractor\n"
) +
gsi::factory_ext ("extract", &rex_extract, gsi::arg ("tech_stack"), gsi::arg ("geo"), gsi::arg ("vertex_ports"), gsi::arg ("polygon_ports"),
"@brief Runs the extraction on the given multi-layer geometry\n"
"See the description of the class for more details."
),
"@brief The network R extractor class\n"
"\n"
"This class provides the algorithms for extracting a R network from a multi-layer arrangement of conductors and vias.\n"
"The main feature is the \\extract method. It takes a multi-layer geometry, a tech stack and a number of port definitions\n"
"and returns a R network. The nodes in that network are annotated, so the corresponding port can be deduced from a node of\n"
"VertexPort or PolygonPort type.\n"
"\n"
"Layers are given by layer indexes - those are generic IDs. Every layer has to be given a unique ID, which must be used throughout "
"the different specifications (geometry, vias, conductors, ports).\n"
"\n"
"Two kind of ports are provided: point-like vertex ports and polygon ports. Polygons for polygon ports should be convex and sit inside "
"the geometry they mark. Ports become nodes in the network. Beside ports, the network can have internal nodes. Nodes are annotated with "
"a type (vertex, polygon, internal) and an index and layer. The layer is the layer ID, the index specifies the position of the "
"corresponding port in the 'vertex_ports' or 'polygon_ports' list of the \\extract call.\n"
"\n"
"This class has been introduced in version 0.30.2\n"
);
}

View File

@ -0,0 +1,412 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "gsiDecl.h"
#include "pexRExtractor.h"
#include "pexSquareCountingRExtractor.h"
#include "pexTriangulationRExtractor.h"
#include "gsiEnums.h"
namespace gsi
{
class RNode
{
public:
~RNode () { }
pex::RNode::node_type type () const { return checked_pointer ()->type; }
db::DBox location () const { return checked_pointer ()->location; }
unsigned int port_index () const { return checked_pointer ()->port_index; }
unsigned int layer () const { return checked_pointer ()->layer; }
std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); }
size_t obj_id () const
{
return size_t (mp_ptr);
}
static RNode *make_node_object (const pex::RNode *node)
{
return new RNode (node);
}
const pex::RNode *checked_pointer () const
{
if (! mp_graph.get ()) {
throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid")));
}
return mp_ptr;
}
pex::RNode *checked_pointer ()
{
if (! mp_graph.get ()) {
throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid")));
}
return const_cast<pex::RNode *> (mp_ptr);
}
private:
tl::weak_ptr<pex::RNetwork> mp_graph;
const pex::RNode *mp_ptr;
RNode (const pex::RNode *node)
: mp_graph (node->graph ()),
mp_ptr (node)
{
// .. nothing yet ..
}
};
class RElement
{
public:
~RElement () { }
double conductance () const { return checked_pointer ()->conductance; }
double resistance () const { return checked_pointer ()->resistance (); }
RNode *a () const { return RNode::make_node_object (checked_pointer ()->a ()); }
RNode *b () const { return RNode::make_node_object (checked_pointer ()->b ()); }
std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); }
size_t obj_id () const
{
return size_t (mp_ptr);
}
static RElement *make_element_object (const pex::RElement *element)
{
return new RElement (element);
}
const pex::RElement *checked_pointer () const
{
if (! mp_graph.get ()) {
throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid")));
}
return mp_ptr;
}
pex::RElement *checked_pointer ()
{
if (! mp_graph.get ()) {
throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid")));
}
return const_cast<pex::RElement *> (mp_ptr);
}
private:
tl::weak_ptr<pex::RNetwork> mp_graph;
const pex::RElement *mp_ptr;
RElement (const pex::RElement *node)
: mp_graph (node->graph ()),
mp_ptr (node)
{
// .. nothing yet ..
}
};
class RElementIterator
{
public:
typedef std::list<const pex::RElement *>::const_iterator basic_iter;
typedef std::forward_iterator_tag iterator_category;
typedef RElement *value_type;
typedef RElement *reference;
typedef void pointer;
typedef void difference_type;
RElementIterator (basic_iter it)
: m_it (it)
{ }
bool operator== (const RElementIterator &it) const { return m_it == it.m_it; }
void operator++ () { ++m_it; }
RElement *operator* () const
{
return RElement::make_element_object (*m_it);
}
private:
basic_iter m_it;
};
static RElementIterator begin_node_elements (RNode *node)
{
return RElementIterator (node->checked_pointer ()->elements ().begin ());
}
static RElementIterator end_network_elements (RNode *node)
{
return RElementIterator (node->checked_pointer ()->elements ().end ());
}
gsi::Enum<pex::RNode::node_type> decl_NodeType ("pex", "RNodeType",
gsi::enum_const ("Internal", pex::RNode::Internal,
"@brief Specifies an internal node in a R network\n"
"Internal nodes are generated during the R extraction process. "
"The port index of such a node is an arbitrary index."
) +
gsi::enum_const ("VertexPort", pex::RNode::VertexPort,
"@brief Specifies a vertex port node in a R network\n"
"Vertex port nodes are generated for vertex ports in \\RExtractor#extract, see 'vertex_ports' argument. "
"The port index of such a node refers to the position in that list."
) +
gsi::enum_const ("PolygonPort", pex::RNode::PolygonPort,
"@brief Specifies a polygon port node in a R network\n"
"Polygon port nodes are generated for polygon ports in \\RExtractor#extract, see 'polygon_ports' argument. "
"The port index of such a node refers to the position in that list."
),
"@brief This class represents the node type for RNode.\n"
"\n"
"This class has been introduced in version 0.30.2"
);
Class<RNode> decl_RNode ("pex", "RNode",
gsi::method ("object_id", &RNode::obj_id,
"@brief Returns an ID representing the actual object\n"
"For every call, a new instance of this object is created, while multiple "
"ones may represent the same internal object. The 'object_id' is a ID that "
"indicates the internal object. Same object_id means same node."
) +
gsi::method ("to_s", &RNode::to_string, gsi::arg ("with_coords", false),
"@brief Returns a string representation of this object\n"
"Nodes are printed with coordinates with 'with_coords' is true."
) +
gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_node_elements, &end_network_elements,
"@brief Iterates the \\RElement objects attached to the node\n"
) +
gsi::method ("type", &RNode::type,
"@brief Gets the type attribute of the node\n"
) +
gsi::method ("location", &RNode::location,
"@brief Gets the location attribute of the node\n"
"The location defined the original position of the node"
) +
gsi::method ("port_index", &RNode::port_index,
"@brief Gets the port index of the node\n"
"The port index associates a node with a original port definition."
) +
gsi::method ("layer", &RNode::layer,
"@brief Gets the Layer ID of the node\n"
"The port index associates a node with a original port definition layer-wise."
),
"@brief Represents a node in a R network graph\n"
"See \\RNetwork for a description of this object\n"
"\n"
"This class has been introduced in version 0.30.2"
);
// Inject the RNode::node_type declarations into RNode
gsi::ClassExt<RNode> inject_NodeType_in_RNode (decl_NodeType.defs ());
Class<RElement> decl_RElement ("pex", "RElement",
gsi::method ("object_id", &RElement::obj_id,
"@brief Returns an ID representing the actual object\n"
"For every call, a new instance of this object is created, while multiple "
"ones may represent the same internal object. The 'object_id' is a ID that "
"indicates the internal object. Same object_id means same element."
) +
gsi::method ("to_s", &RElement::to_string, gsi::arg ("with_coords", false),
"@brief Returns a string representation of this object\n"
"Nodes are printed with coordinates with 'with_coords' is true."
) +
gsi::method ("resistance", &RElement::resistance,
"@brief Gets the resistance value of the object\n"
) +
gsi::factory ("a", &RElement::a,
"@brief Gets the first node the element connects\n"
) +
gsi::factory ("b", &RElement::b,
"@brief Gets the second node the element connects\n"
),
"@brief Represents an edge (also called element) in a R network graph\n"
"See \\RNetwork for a description of this object"
"\n"
"This class has been introduced in version 0.30.2"
);
static RNode *create_node (pex::RNetwork *network, pex::RNode::node_type type, unsigned int port_index, unsigned int layer)
{
return RNode::make_node_object (network->create_node (type, port_index, layer));
}
static RElement *create_element (pex::RNetwork *network, double r, RNode *a, RNode *b)
{
double s = fabs (r) < 1e-10 ? pex::RElement::short_value () : 1.0 / r;
return RElement::make_element_object (network->create_element (s, a->checked_pointer (), b->checked_pointer ()));
}
static void remove_element (pex::RNetwork *network, RElement *element)
{
network->remove_element (element->checked_pointer ());
}
static void remove_node (pex::RNetwork *network, RNode *node)
{
network->remove_node (node->checked_pointer ());
}
class NetworkElementIterator
{
public:
typedef pex::RNetwork::element_iterator basic_iter;
typedef std::forward_iterator_tag iterator_category;
typedef RElement *value_type;
typedef RElement *reference;
typedef void pointer;
typedef void difference_type;
NetworkElementIterator (basic_iter it)
: m_it (it)
{ }
bool operator== (const NetworkElementIterator &it) const { return m_it == it.m_it; }
void operator++ () { ++m_it; }
RElement *operator* () const
{
return RElement::make_element_object (m_it.operator-> ());
}
private:
basic_iter m_it;
};
static NetworkElementIterator begin_network_elements (pex::RNetwork *network)
{
return NetworkElementIterator (network->begin_elements ());
}
static NetworkElementIterator end_network_elements (pex::RNetwork *network)
{
return NetworkElementIterator (network->end_elements ());
}
class NetworkNodeIterator
{
public:
typedef pex::RNetwork::node_iterator basic_iter;
typedef std::forward_iterator_tag iterator_category;
typedef RNode *value_type;
typedef RNode *reference;
typedef void pointer;
typedef void difference_type;
NetworkNodeIterator (basic_iter it)
: m_it (it)
{ }
bool operator== (const NetworkNodeIterator &it) const { return m_it == it.m_it; }
void operator++ () { ++m_it; }
RNode *operator* () const
{
return RNode::make_node_object (m_it.operator-> ());
}
private:
basic_iter m_it;
};
static NetworkNodeIterator begin_network_nodes (pex::RNetwork *network)
{
return NetworkNodeIterator (network->begin_nodes ());
}
static NetworkNodeIterator end_network_nodes (pex::RNetwork *network)
{
return NetworkNodeIterator (network->end_nodes ());
}
Class<pex::RNetwork> decl_RNetwork ("pex", "RNetwork",
gsi::factory_ext ("create_node", &create_node, gsi::arg ("type"), gsi::arg ("port_index"), gsi::arg ("layer", (unsigned int) 0),
"@brief Creates a new node with the given type and index'.\n"
"@return A reference to the new nbode object."
) +
gsi::factory_ext ("create_element", &create_element, gsi::arg ("resistance"), gsi::arg ("a"), gsi::arg ("b"),
"@brief Creates a new element between the nodes given by 'a' abd 'b'.\n"
"If a resistor already exists between the two nodes, both resistors are combined into one.\n"
"@return A reference to the new resistor object."
) +
gsi::method_ext ("remove_element", &remove_element, gsi::arg ("element"),
"@brief Removes the given element\n"
"If removing the element renders an internal node orphan (i.e. without elements), this "
"node is removed too."
) +
gsi::method_ext ("remove_node", &remove_node, gsi::arg ("node"),
"@brief Removes the given node\n"
"Only internal nodes can be removed. Removing a node will also remove the "
"elements attached to this node."
) +
gsi::method ("clear", &pex::RNetwork::clear,
"@brief Clears the network\n"
) +
gsi::method ("simplify", &pex::RNetwork::simplify,
"@brief Simplifies the network\n"
"\n"
"This will:\n"
"@ul\n"
"@li Join serial resistors if connected by an internal node @/li\n"
"@li Remove shorts and join the nodes, if one of them is\n"
" an internal node. The non-internal node will persist @/li\n"
"@li Remove \"dangling\" resistors if the dangling node is\n"
" an internal one @/li\n"
"@/ul\n"
) +
gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_network_elements, &end_network_elements,
"@brief Iterates the \\RElement objects inside the network\n"
) +
gsi::iterator_ext ("each_node", gsi::return_new_object (), &begin_network_nodes, &end_network_nodes,
"@brief Iterates the \\RNode objects inside the network\n"
) +
gsi::method ("num_nodes", &pex::RNetwork::num_nodes,
"@brief Gets the total number of nodes in the network\n"
) +
gsi::method ("num_internal_nodes", &pex::RNetwork::num_internal_nodes,
"@brief Gets the number of internal nodes in the network\n"
) +
gsi::method ("num_elements", &pex::RNetwork::num_elements,
"@brief Gets the number of elements in the network\n"
) +
gsi::method ("to_s", &pex::RNetwork::to_string, gsi::arg ("with_coords", false),
"@brief Returns a string representation of the network\n"
"Nodes are printed with coordinates with 'with_coords' is true."
),
"@brief Represents a network of resistors\n"
"\n"
"The network is basically a graph with nodes and edges (the resistors). "
"The resistors are called 'elements' and are represented by \\RElement objects. "
"The nodes are represented by \\RNode objects. "
"The network is created by \\RExtractor#extract, which turns a polygon into a resistor network.\n"
"\n"
"This class has been introduced in version 0.30.2\n"
);
}

35
src/pex/pex/pex.pro Normal file
View File

@ -0,0 +1,35 @@
DESTDIR = $$OUT_PWD/../..
TARGET = klayout_pex
include($$PWD/../../lib.pri)
DEFINES += MAKE_PEX_LIBRARY
SOURCES = \
gsiDeclRNetExtractor.cc \
gsiDeclRNetwork.cc \
pexForceLink.cc \
pexRExtractor.cc \
gsiDeclRExtractor.cc \
pexRExtractorTech.cc \
pexRNetExtractor.cc \
pexRNetwork.cc \
pexSquareCountingRExtractor.cc \
pexTriangulationRExtractor.cc
HEADERS = \
pexForceLink.h \
pexRExtractor.h \
pexRExtractorTech.h \
pexRNetExtractor.h \
pexRNetwork.h \
pexSquareCountingRExtractor.h \
pexTriangulationRExtractor.h
RESOURCES = \
INCLUDEPATH += $$TL_INC $$GSI_INC $$DB_INC
DEPENDPATH += $$TL_INC $$GSI_INC $$DB_INC
LIBS += -L$$DESTDIR -lklayout_tl -lklayout_gsi -lklayout_db

51
src/pex/pex/pexCommon.h Normal file
View File

@ -0,0 +1,51 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if !defined(HDR_pexCommon_h)
# define HDR_pexCommon_h
# if defined _WIN32 || defined __CYGWIN__
# ifdef MAKE_PEX_LIBRARY
# define PEX_PUBLIC __declspec(dllexport)
# else
# define PEX_PUBLIC __declspec(dllimport)
# endif
# define PEX_LOCAL
# define PEX_PUBLIC_TEMPLATE
# else
# if __GNUC__ >= 4 || defined(__clang__)
# define PEX_PUBLIC __attribute__ ((visibility ("default")))
# define PEX_PUBLIC_TEMPLATE __attribute__ ((visibility ("default")))
# define PEX_LOCAL __attribute__ ((visibility ("hidden")))
# else
# define PEX_PUBLIC
# define PEX_PUBLIC_TEMPLATE
# define PEX_LOCAL
# endif
# endif
#endif

View File

@ -0,0 +1,32 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexForceLink.h"
namespace pex
{
int _force_link_f ()
{
return 0;
}
}

View File

@ -0,0 +1,40 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_pexForceLink
#define HDR_pexForceLink
#include "pexCommon.h"
/**
* @file Include this function to force linking of the pex module
*/
namespace pex
{
PEX_PUBLIC int _force_link_f ();
static int _force_link_target = _force_link_f ();
}
#endif

View File

@ -0,0 +1,39 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexRExtractor.h"
namespace pex
{
RExtractor::RExtractor ()
{
// .. nothing yet ..
}
RExtractor::~RExtractor ()
{
// .. nothing yet ..
}
}

View File

@ -0,0 +1,71 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_pexRExtractor
#define HDR_pexRExtractor
#include "pexCommon.h"
#include "dbPolygon.h"
namespace pex
{
class RNetwork;
/**
* @brief A base class for an resistance extractor
*
* The R extractor takes a polyon, a technology definition
* and port definitions and extracts a resistor network.
*
* Ports are points or polygons that define the connection
* points to the network.
*/
class PEX_PUBLIC RExtractor
{
public:
/**
* @brief Constructor
*/
RExtractor ();
/**
* @brief Destructor
*/
virtual ~RExtractor ();
/**
* @brief Extracts the resistance network from the given polygon and ports
*
* The ports define specific locations where to connect to the resistance network.
* The network will contain corresponding nodes with the VertexPort for vertex ports
* and PolygonPort for polygon port. The node index is the index in the respective
* lists.
*/
virtual void extract (const db::Polygon &polygon, const std::vector<db::Point> &vertex_ports, const std::vector<db::Polygon> &polygon_ports, RNetwork &rnetwork) = 0;
};
}
#endif

View File

@ -0,0 +1,97 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexRExtractorTech.h"
#include "tlString.h"
namespace pex
{
// ------------------------------------------------------------------
std::string
RExtractorTechVia::to_string () const
{
std::string res = "Via(";
res += tl::sprintf ("bottom=L%u, cut=L%u, top=L%u, R=%.12g \xC2\xB5m\xC2\xB2*Ohm", bottom_conductor, cut_layer, top_conductor, resistance);
if (merge_distance > 1e-10) {
res += tl::sprintf(", d_merge=%.12g \xC2\xB5m", merge_distance);
}
res += ")";
return res;
}
// ------------------------------------------------------------------
std::string
RExtractorTechConductor::to_string () const
{
std::string res = "Conductor(";
res += tl::sprintf ("layer=L%u, R=%.12g Ohm/sq", layer, resistance);
switch (algorithm) {
case SquareCounting:
res += ", algo=SquareCounting";
break;
case Tesselation:
res += ", algo=Tesselation";
break;
default:
break;
}
if (triangulation_min_b > 1e-10) {
res += tl::sprintf(", tri_min_b=%.12g \xC2\xB5m", triangulation_min_b);
}
if (triangulation_max_area > 1e-10) {
res += tl::sprintf(", tri_max_area=%.12g \xC2\xB5m\xC2\xB2", triangulation_max_area);
}
res += ")";
return res;
}
// ------------------------------------------------------------------
RExtractorTech::RExtractorTech ()
: skip_simplify (false)
{
// .. nothing yet ..
}
std::string
RExtractorTech::to_string () const
{
std::string res;
if (skip_simplify) {
res += "skip_simplify=true\n";
}
res += tl::join (vias.begin (), vias.end (), "\n");
res += "\n";
res += tl::join (conductors.begin (), conductors.end (), "\n");
return res;
}
}

View File

@ -0,0 +1,193 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_pexRExtractorTech
#define HDR_pexRExtractorTech
#include "pexCommon.h"
#include <list>
#include <string>
namespace pex
{
/**
* @brief Specifies the extraction parameters for vias
*
* Note that the layers are generic IDs. These are usigned ints specifying
* a layer.
*/
class PEX_PUBLIC RExtractorTechVia
{
public:
RExtractorTechVia ()
: cut_layer (0), top_conductor (0), bottom_conductor (0), resistance (0.0), merge_distance (0.0)
{
// .. nothing yet ..
}
/**
* @brief Returns a string describing this object
*/
std::string to_string () const;
/**
* @brief Specifies the cut layer
* This is the layer the via sits on
*/
unsigned int cut_layer;
/**
* @brief Specifies the top conductor
* The value is the ID of the top conductor layer
*/
unsigned int top_conductor;
/**
* @brief Specifies the bottom conductor
* The value is the ID of the bottom conductor layer
*/
unsigned int bottom_conductor;
/**
* @brief Specifies the resistance in Ohm * sqaure micrometer
*/
double resistance;
/**
* @brief Specifies the merge distance in micrometers
* The merge distance indicates a range under which vias are merged
* into bigger effective areas to reduce the complexity of via arrays.
*/
double merge_distance;
};
/**
* @brief Specifies the extraction parameters for a conductor layer
*
* Note that the layers are generic IDs. These are usigned ints specifying
* a layer.
*/
class PEX_PUBLIC RExtractorTechConductor
{
public:
/**
* @brief A algorithm to use
*/
enum Algorithm
{
/**
* @brief The square counting algorithm
* This algorithm is suitable for "long and thin" wires.
*/
SquareCounting = 0,
/**
* @brief The tesselation algorithm
* This algorithm is suitable to "large" sheets, specifically substrate.
*/
Tesselation = 1
};
/**
* @brief The constructor
*/
RExtractorTechConductor ()
: layer (0), resistance (0.0), algorithm (SquareCounting), triangulation_min_b (-1.0), triangulation_max_area (-1.0)
{
// .. nothing yet ..
}
/**
* @brief Returns a string describing this object
*/
std::string to_string () const;
/**
* @brief Specifies the layer
* The value is the generic ID of the layer.
*/
unsigned int layer;
/**
* @brief Specifies the sheet resistance
* The sheet resistance is given in units of Ohm / square
*/
double resistance;
/**
* @brief The algorihm to use
*/
Algorithm algorithm;
/**
* @brief The "min_b" parameter for the triangulation
* The "b" parameter is a ratio of shortest triangle edge to circle radius.
* If a negative value is given, the default value is taken.
*/
double triangulation_min_b;
/**
* @brief The "max_area" parameter for the triangulation
* The "max_area" specifies the maximum area of the triangles produced in square micrometers.
* If a negative value is given, the default value is taken.
*/
double triangulation_max_area;
};
/**
* @brief Specifies the extraction parameters
*/
class PEX_PUBLIC RExtractorTech
{
public:
/**
* @brief Constructor
*/
RExtractorTech ();
/**
* @brief Returns a string describing this object
*/
std::string to_string () const;
/**
* @brief A list of via definitions
*/
std::list<RExtractorTechVia> vias;
/**
* @brief A list of conductor definitions
*/
std::list<RExtractorTechConductor> conductors;
/**
* @brief A flag indicating to skip the simplify step after extraction
*/
bool skip_simplify;
};
}
#endif

View File

@ -0,0 +1,476 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexCommon.h"
#include "pexRNetExtractor.h"
#include "pexRNetwork.h"
#include "pexRExtractorTech.h"
#include "pexSquareCountingRExtractor.h"
#include "pexTriangulationRExtractor.h"
#include "dbBoxScanner.h"
#include "dbPolygonTools.h"
#include "dbRegionProcessors.h"
#include "dbCompoundOperation.h"
#include "dbPolygonNeighborhood.h"
#include "dbPropertiesRepository.h"
namespace pex
{
RNetExtractor::RNetExtractor (double dbu)
: m_dbu (dbu)
{
// .. nothing yet ..
}
void
RNetExtractor::extract (const RExtractorTech &tech,
const std::map<unsigned int, db::Region> &geo,
const std::map<unsigned int, std::vector<db::Point> > &vertex_ports,
const std::map<unsigned int, std::vector<db::Polygon> > &polygon_ports,
RNetwork &rnetwork)
{
rnetwork.clear ();
std::map<unsigned int, std::vector<ViaPort> > via_ports;
create_via_ports (tech, geo, via_ports, rnetwork);
for (auto g = geo.begin (); g != geo.end (); ++g) {
// Find the conductor spec for the given layer
const RExtractorTechConductor *cond = 0;
for (auto c = tech.conductors.begin (); c != tech.conductors.end () && !cond; ++c) {
if (c->layer == g->first) {
cond = c.operator-> ();
}
}
if (! cond) {
continue;
}
// fetch the port list for vertex ports
auto ivp = vertex_ports.find (g->first);
static std::vector<db::Point> empty_vertex_ports;
const std::vector<db::Point> &vp = ivp == vertex_ports.end () ? empty_vertex_ports : ivp->second;
// fetch the port list for polygon ports
auto ipp = polygon_ports.find (g->first);
static std::vector<db::Polygon> empty_polygon_ports;
const std::vector<db::Polygon> &pp = ipp == polygon_ports.end () ? empty_polygon_ports : ipp->second;
// fetch the port list for via ports
auto iviap = via_ports.find (g->first);
static std::vector<ViaPort> empty_via_ports;
const std::vector<ViaPort> &viap = iviap == via_ports.end () ? empty_via_ports : iviap->second;
// extract the conductor polygon and integrate the results into the target network
extract_conductor (*cond, g->second, vp, pp, viap, rnetwork);
}
if (! tech.skip_simplify) {
rnetwork.simplify ();
}
}
static double
via_conductance (const RExtractorTechVia &via_tech,
const db::Polygon &poly,
double dbu)
{
if (via_tech.resistance < 1e-10) {
return RElement::short_value ();
} else {
return (1.0 / via_tech.resistance) * dbu * dbu * poly.area ();
}
}
namespace
{
class ViaAggregationVisitor
: public db::PolygonNeighborhoodVisitor
{
public:
ViaAggregationVisitor (const RExtractorTechVia *via_tech, double dbu)
: mp_via_tech (via_tech), m_dbu (dbu)
{
// this is just for consistency - we actually do not produce output
set_result_type (db::CompoundRegionCheckOperationNode::Region);
}
virtual void neighbors (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::PolygonWithProperties &polygon, const neighbors_type &neighbors)
{
auto i = neighbors.find ((unsigned int) 1);
if (i == neighbors.end ()) {
return;
}
double c = 0;
for (auto vp = i->second.begin (); vp != i->second.end (); ++vp) {
double cc = via_conductance (*mp_via_tech, *vp, m_dbu);
if (cc == RElement::short_value ()) {
c = cc;
break;
} else {
c += cc;
}
}
db::PropertiesSet ps;
ps.insert (prop_name_id, tl::Variant (c));
output_polygon (db::PolygonWithProperties (polygon, db::properties_id (ps)));
}
static db::property_names_id_type prop_name_id;
private:
const RExtractorTechVia *mp_via_tech;
std::vector<std::pair<double, db::Point> > *mp_conductances;
db::property_names_id_type m_prop_name_id;
double m_dbu;
};
db::property_names_id_type ViaAggregationVisitor::prop_name_id = db::property_names_id (tl::Variant ());
}
void
RNetExtractor::create_via_port (const pex::RExtractorTechVia &tech,
double conductance,
const db::Polygon &poly,
unsigned int &port_index,
std::map<unsigned int, std::vector<ViaPort> > &vias,
RNetwork &rnetwork)
{
RNode *a = rnetwork.create_node (RNode::Internal, port_index++, tech.bottom_conductor);
RNode *b = rnetwork.create_node (RNode::Internal, port_index++, tech.top_conductor);
db::CplxTrans to_um (m_dbu);
db::Box box = poly.box ();
b->location = a->location = to_um * box;
rnetwork.create_element (conductance, a, b);
vias[tech.bottom_conductor].push_back (ViaPort (box.center (), a));
vias[tech.top_conductor].push_back (ViaPort (box.center (), b));
}
void
RNetExtractor::create_via_ports (const RExtractorTech &tech,
const std::map<unsigned int, db::Region> &geo,
std::map<unsigned int, std::vector<ViaPort> > &vias,
RNetwork &rnetwork)
{
unsigned int port_index = 0;
for (auto v = tech.vias.begin (); v != tech.vias.end (); ++v) {
auto g = geo.find (v->cut_layer);
if (g == geo.end ()) {
continue;
}
if (v->merge_distance > db::epsilon) {
// with merge, follow this scheme:
// 1.) do a merge by over/undersize
// 2.) do a convex decomposition, so we get convex via shapes with the bbox center inside the polygon
// 3.) re-aggregate the original via polygons and collect the total conductance per merged shape
db::Coord sz = db::coord_traits<db::Coord>::rounded (0.5 * v->merge_distance / m_dbu);
db::Region merged_vias = g->second.sized (sz).sized (-sz);
merged_vias.process (db::ConvexDecomposition (db::PO_any));
std::vector<db::CompoundRegionOperationNode *> children;
children.push_back (new db::CompoundRegionOperationPrimaryNode ());
children.push_back (new db::CompoundRegionOperationSecondaryNode (const_cast<db::Region *> (&g->second)));
ViaAggregationVisitor visitor (v.operator-> (), m_dbu);
db::PolygonNeighborhoodCompoundOperationNode en_node (children, &visitor, 0);
auto aggregated = merged_vias.cop_to_region (en_node);
for (auto p = aggregated.begin (); ! p.at_end (); ++p) {
double c = db::properties (p.prop_id ()).value (ViaAggregationVisitor::prop_name_id).to_double ();
create_via_port (*v, c, *p, port_index, vias, rnetwork);
}
} else {
for (auto p = g->second.begin_merged (); ! p.at_end (); ++p) {
create_via_port (*v, via_conductance (*v, *p, m_dbu), *p, port_index, vias, rnetwork);
}
}
}
}
static inline size_t make_id (unsigned int index, unsigned int type)
{
return (size_t (index) << 2) + type;
}
static inline unsigned int index_from_id (size_t id)
{
return (unsigned int) (id >> 2);
}
static inline unsigned int type_from_id (size_t id)
{
return (unsigned int) (id & 3);
}
namespace
{
class ExtractingReceiver
: public db::box_scanner_receiver2<db::Polygon, size_t, db::Box, size_t>
{
public:
ExtractingReceiver (const RExtractorTechConductor *cond,
const std::vector<db::Point> *vertex_ports,
const std::vector<db::Polygon> *polygon_ports,
const std::vector<RNetExtractor::ViaPort> *via_ports,
double dbu,
RNetwork *rnetwork)
: mp_cond (cond),
mp_vertex_ports (vertex_ports),
mp_polygon_ports (polygon_ports),
mp_via_ports (via_ports),
m_next_internal_port_index (0),
m_dbu (dbu),
mp_rnetwork (rnetwork)
{
for (auto n = rnetwork->begin_nodes (); n != rnetwork->end_nodes (); ++n) {
if (n->type == RNode::Internal && n->port_index > m_next_internal_port_index) {
m_next_internal_port_index = n->port_index;
}
}
}
void finish1 (const db::Polygon *poly, const size_t poly_id)
{
auto i = m_interacting_ports.find (poly_id);
if (i == m_interacting_ports.end ()) {
static std::set<size_t> empty_ids;
extract (*poly, empty_ids);
} else {
extract (*poly, i->second);
m_interacting_ports.erase (i);
}
}
void add (const db::Polygon *poly, const size_t poly_id, const db::Box *port, const size_t port_id)
{
if (db::interact (*poly, *port)) {
m_interacting_ports[poly_id].insert (port_id);
}
}
private:
std::map<size_t, std::set<size_t> > m_interacting_ports;
const RExtractorTechConductor *mp_cond;
const std::vector<db::Point> *mp_vertex_ports;
const std::vector<db::Polygon> *mp_polygon_ports;
const std::vector<RNetExtractor::ViaPort> *mp_via_ports;
std::map<size_t, RNode *> m_id_to_node;
unsigned int m_next_internal_port_index;
double m_dbu;
RNetwork *mp_rnetwork;
void extract (const db::Polygon &poly, const std::set<size_t> &port_ids)
{
std::vector<db::Point> local_vertex_ports;
std::vector<size_t> local_vertex_port_ids;
std::vector<db::Polygon> local_polygon_ports;
std::vector<size_t> local_polygon_port_ids;
for (auto i = port_ids.begin (); i != port_ids.end (); ++i) {
switch (type_from_id (*i)) {
case 0: // vertex port
local_vertex_port_ids.push_back (*i);
local_vertex_ports.push_back ((*mp_vertex_ports) [index_from_id (*i)]);
break;
case 1: // via port
local_vertex_port_ids.push_back (*i);
local_vertex_ports.push_back ((*mp_via_ports) [index_from_id (*i)].position);
break;
case 2: // polygon port
local_polygon_port_ids.push_back (*i);
local_polygon_ports.push_back ((*mp_polygon_ports) [index_from_id (*i)]);
break;
}
}
pex::RNetwork local_network;
switch (mp_cond->algorithm) {
case RExtractorTechConductor::SquareCounting:
default:
{
pex::SquareCountingRExtractor rex (m_dbu);
rex.extract (poly, local_vertex_ports, local_polygon_ports, local_network);
}
break;
case RExtractorTechConductor::Tesselation:
{
pex::TriangulationRExtractor rex (m_dbu);
rex.extract (poly, local_vertex_ports, local_polygon_ports, local_network);
}
break;
}
integrate (local_network, local_vertex_port_ids, local_polygon_port_ids);
}
void integrate (const RNetwork &local_network,
const std::vector<size_t> &local_vertex_port_ids,
const std::vector<size_t> &local_polygon_port_ids)
{
// create or find the new nodes in the target network
std::unordered_map<const RNode *, RNode *> n2n;
for (auto n = local_network.begin_nodes (); n != local_network.end_nodes (); ++n) {
const RNode *local = n.operator-> ();
RNode *global = 0;
if (local->type == RNode::Internal) {
// for internal nodes always create a node in the target network
global = mp_rnetwork->create_node (local->type, ++m_next_internal_port_index, mp_cond->layer);
global->location = local->location;
} else if (local->type == RNode::VertexPort) {
// for vertex nodes reuse the via node or create a new target node, unless one
// was created already.
size_t id = local_vertex_port_ids [local->port_index];
auto i2n = m_id_to_node.find (id);
if (i2n != m_id_to_node.end ()) {
global = i2n->second;
} else {
if (type_from_id (id) == 0) { // vertex port
global = mp_rnetwork->create_node (RNode::VertexPort, index_from_id (id), mp_cond->layer);
global->location = local->location;
} else if (type_from_id (id) == 1) { // via port
global = (*mp_via_ports) [index_from_id (id)].node;
}
m_id_to_node.insert (std::make_pair (id, global));
}
} else if (local->type == RNode::PolygonPort) {
// for polygon nodes create a new target node, unless one was created already.
size_t id = local_polygon_port_ids [local->port_index];
tl_assert (type_from_id (id) == 2);
auto i2n = m_id_to_node.find (id);
if (i2n != m_id_to_node.end ()) {
global = i2n->second;
} else {
global = mp_rnetwork->create_node (RNode::PolygonPort, index_from_id (id), mp_cond->layer);
global->location = local->location;
m_id_to_node.insert (std::make_pair (id, global));
}
}
tl_assert (global != 0);
n2n.insert (std::make_pair (local, global));
}
// create the R elements in the target network
for (auto e = local_network.begin_elements (); e != local_network.end_elements (); ++e) {
const RElement *local = e.operator-> ();
auto ia = n2n.find (local->a ());
auto ib = n2n.find (local->b ());
tl_assert (ia != n2n.end ());
tl_assert (ia != n2n.end ());
double c;
if (mp_cond->resistance < 1e-10) {
c = RElement::short_value ();
} else {
c = local->conductance / mp_cond->resistance;
}
mp_rnetwork->create_element (c, ia->second, ib->second);
}
}
};
}
void
RNetExtractor::extract_conductor (const RExtractorTechConductor &cond,
const db::Region &region,
const std::vector<db::Point> &vertex_ports,
const std::vector<db::Polygon> &polygon_ports,
const std::vector<ViaPort> &via_ports,
RNetwork &rnetwork)
{
db::box_scanner2<db::Polygon, size_t, db::Box, size_t> scanner;
size_t poly_id = 0;
for (auto p = region.addressable_merged_polygons (); ! p.at_end (); ++p) {
scanner.insert1 (p.operator-> (), poly_id++);
}
std::list<db::Box> box_heap;
// type 0 objects (vertex ports)
for (auto i = vertex_ports.begin (); i != vertex_ports.end (); ++i) {
// TODO: could be without enlarge?
box_heap.push_back (db::Box (*i, *i).enlarged (db::Vector (1, 1)));
scanner.insert2 (&box_heap.back (), make_id (i - vertex_ports.begin (), 0));
}
// type 1 objects (via ports)
for (auto i = via_ports.begin (); i != via_ports.end (); ++i) {
// TODO: could be without enlarge?
box_heap.push_back (db::Box (i->position, i->position).enlarged (db::Vector (1, 1)));
scanner.insert2 (&box_heap.back (), make_id (i - via_ports.begin (), 1));
}
// type 2 objects (polygon ports)
for (auto i = polygon_ports.begin (); i != polygon_ports.end (); ++i) {
box_heap.push_back (i->box ());
scanner.insert2 (&box_heap.back (), make_id (i - polygon_ports.begin (), 2));
}
ExtractingReceiver rec (&cond, &vertex_ports, &polygon_ports, &via_ports, m_dbu, &rnetwork);
scanner.process (rec, 0, db::box_convert<db::Polygon> (), db::box_convert<db::Box> ());
}
}

View File

@ -0,0 +1,104 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_pexRNetExtractor
#define HDR_pexRNetExtractor
#include "pexCommon.h"
#include "dbRegion.h"
namespace pex
{
class RExtractorTech;
class RExtractorTechVia;
class RExtractorTechConductor;
class RNetwork;
class RNode;
/**
* @brief Implementation of the R extractor for a multi-polygon/multi-layer net
*/
class PEX_PUBLIC RNetExtractor
{
public:
/**
* @brief Constructor
* @param dbu The database unit to be used to convert coordinates into micrometers
*/
RNetExtractor (double dbu);
/**
* @brief Extracts a R network from a given set of geometries and ports
* @param geo The geometries per layer
* @param vertex_ports The vertex ports - a list of layer and points to attache a port to on this layer
* @param polygon_ports The polygon ports - a list of layer and polygons to attach a port to on this layer
* @param rnetwork The network extracted (output)
*
* The network nodes will carry the information about the port, in case they
* have been generated from a port.
*/
void extract (const RExtractorTech &tech,
const std::map<unsigned int, db::Region> &geo,
const std::map<unsigned int, std::vector<db::Point> > &vertex_ports,
const std::map<unsigned int, std::vector<db::Polygon> > &polygon_ports,
RNetwork &rnetwork);
/**
* @brief A structure describing a via port
* This structure is used internally
*/
struct ViaPort
{
ViaPort () : node (0) { }
ViaPort (const db::Point &p, RNode *n) : position (p), node (n) { }
db::Point position;
RNode *node;
};
protected:
void create_via_ports (const RExtractorTech &tech,
const std::map<unsigned int, db::Region> &geo,
std::map<unsigned int, std::vector<ViaPort> > &vias,
RNetwork &rnetwork);
void extract_conductor (const RExtractorTechConductor &cond,
const db::Region &region,
const std::vector<db::Point> &vertex_ports,
const std::vector<db::Polygon> &polygon_ports,
const std::vector<ViaPort> &via_ports,
RNetwork &rnetwork);
private:
double m_dbu;
void create_via_port (const RExtractorTechVia &tech,
double conductance,
const db::Polygon &poly,
unsigned int &port_index,
std::map<unsigned int, std::vector<ViaPort> > &vias,
RNetwork &rnetwork);
};
}
#endif

320
src/pex/pex/pexRNetwork.cc Normal file
View File

@ -0,0 +1,320 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexRNetwork.h"
#include "tlEquivalenceClusters.h"
namespace pex
{
// -----------------------------------------------------------------------------
std::string
RNode::to_string (bool with_coords) const
{
std::string res;
switch (type) {
default:
res += "$";
break;
case VertexPort:
res += "V";
break;
case PolygonPort:
res += "P";
break;
}
res += tl::to_string (port_index);
if (layer > 0) {
res += ".";
res += tl::to_string (layer);
}
if (with_coords) {
res += location.to_string ();
}
return res;
}
// -----------------------------------------------------------------------------
std::string
RElement::to_string (bool with_coords) const
{
std::string na;
if (a ()) {
na = a ()->to_string (with_coords);
} else {
na = "(nil)";
}
std::string nb;
if (b ()) {
nb = b ()->to_string (with_coords);
} else {
nb = "(nil)";
}
if (nb < na) {
std::swap (na, nb);
}
std::string res = "R " + na + " " + nb + " ";
res += tl::sprintf ("%.6g", resistance ());
return res;
}
// -----------------------------------------------------------------------------
RNetwork::RNetwork ()
{
// .. nothing yet ..
}
RNetwork::~RNetwork ()
{
clear ();
}
std::string
RNetwork::to_string (bool with_coords) const
{
std::string res;
for (auto e = m_elements.begin (); e != m_elements.end (); ++e) {
if (! res.empty ()) {
res += "\n";
}
res += e->to_string (with_coords);
}
return res;
}
void
RNetwork::clear ()
{
m_elements.clear (); // must happen before m_nodes
m_nodes.clear ();
m_elements_by_nodes.clear ();
m_nodes_by_type.clear ();
}
RNode *
RNetwork::create_node (RNode::node_type type, unsigned int port_index, unsigned int layer)
{
if (type != RNode::Internal) {
auto i = m_nodes_by_type.find (std::make_pair (type, std::make_pair (port_index, layer)));
if (i != m_nodes_by_type.end ()) {
return i->second;
} else {
RNode *new_node = new RNode (this, type, db::DBox (), port_index, layer);
m_nodes.push_back (new_node);
m_nodes_by_type.insert (std::make_pair (std::make_pair (type, std::make_pair (port_index, layer)), new_node));
return new_node;
}
} else {
RNode *new_node = new RNode (this, type, db::DBox (), port_index, layer);
m_nodes.push_back (new_node);
return new_node;
}
}
RElement *
RNetwork::create_element (double conductance, RNode *a, RNode *b)
{
std::pair<RNode *, RNode *> key (a, b);
if (size_t (b) < size_t (a)) {
std::swap (key.first, key.second);
}
auto i = m_elements_by_nodes.find (key);
if (i != m_elements_by_nodes.end ()) {
if (conductance == pex::RElement::short_value () || i->second->conductance == pex::RElement::short_value ()) {
i->second->conductance = pex::RElement::short_value ();
} else {
i->second->conductance += conductance;
}
return i->second;
} else {
RElement *element = new RElement (this, conductance, a, b);
m_elements.push_back (element);
m_elements_by_nodes.insert (std::make_pair (key, element));
a->m_elements.push_back (element);
element->m_ia = --a->m_elements.end ();
b->m_elements.push_back (element);
element->m_ib = --b->m_elements.end ();
return element;
}
}
void
RNetwork::remove_node (RNode *node)
{
tl_assert (node->type == RNode::Internal);
while (! node->m_elements.empty ()) {
delete const_cast<RElement *> (node->m_elements.front ());
}
delete node;
}
void
RNetwork::remove_element (RElement *element)
{
RNode *a = const_cast<RNode *> (element->a ());
RNode *b = const_cast<RNode *> (element->b ());
delete element;
if (a && a->type == RNode::Internal && a->m_elements.empty ()) {
delete a;
}
if (b && b->type == RNode::Internal && b->m_elements.empty ()) {
delete b;
}
}
void
RNetwork::join_nodes (RNode *a, RNode *b)
{
for (auto e = b->elements ().begin (); e != b->elements ().end (); ++e) {
RNode *on = const_cast<RNode *> ((*e)->other (b));
if (on != a) {
create_element ((*e)->conductance, on, a);
}
}
a->location += b->location;
remove_node (b);
}
void
RNetwork::simplify ()
{
bool any_change = true;
while (any_change) {
any_change = false;
// join shorted clusters - we take care to remove internal nodes only
tl::equivalence_clusters<const RNode *> clusters;
for (auto e = m_elements.begin (); e != m_elements.end (); ++e) {
if (e->conductance == pex::RElement::short_value () && (e->a ()->type == pex::RNode::Internal || e->b ()->type == pex::RNode::Internal)) {
clusters.same (e->a (), e->b ());
}
}
for (size_t ic = 1; ic <= clusters.size (); ++ic) {
RNode *remaining = 0;
RNode *first_node = 0;
for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) {
RNode *n = const_cast<RNode *> ((*c)->first);
if (! first_node) {
first_node = n;
}
if (n->type != pex::RNode::Internal) {
remaining = n;
break;
}
}
if (! remaining) {
// Only internal nodes
remaining = first_node;
}
for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) {
RNode *n = const_cast<RNode *> ((*c)->first);
if (n != remaining && n->type == pex::RNode::Internal) {
any_change = true;
join_nodes (remaining, n);
}
}
}
// combine serial resistors if connected through an internal node
std::vector<RNode *> nodes_to_remove;
for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) {
size_t nres = n->elements ().size ();
if (n->type == pex::RNode::Internal && nres <= 2) {
any_change = true;
if (nres == 2) {
auto e = n->elements ().begin ();
RNode *n1 = const_cast<RNode *> ((*e)->other (n.operator-> ()));
double r1 = (*e)->resistance ();
++e;
RNode *n2 = const_cast<RNode *> ((*e)->other (n.operator-> ()));
double r2 = (*e)->resistance ();
double r = r1 + r2;
if (r == 0.0) {
create_element (pex::RElement::short_value (), n1, n2);
} else {
create_element (1.0 / r, n1, n2);
}
}
nodes_to_remove.push_back (n.operator-> ());
}
}
for (auto n = nodes_to_remove.begin (); n != nodes_to_remove.end (); ++n) {
remove_node (*n);
}
}
}
}

385
src/pex/pex/pexRNetwork.h Normal file
View File

@ -0,0 +1,385 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_pexRNetwork
#define HDR_pexRNetwork
#include "pexCommon.h"
#include "dbPolygon.h"
#include "dbPLC.h"
#include "tlList.h"
#include <string>
#include <list>
#include <limits>
namespace pex
{
class RElement;
class RNode;
class RNetwork;
/**
* @brief Represents a node in the R graph
*
* A node connects to multiple elements (resistors).
* Every element has two nodes. The nodes and elements form
* a graph.
*
* RNode object cannot be created directly. Use "create_node"
* from RNetwork.
*/
class PEX_PUBLIC RNode
: public tl::list_node<RNode>
{
public:
/**
* @brief The type of the node
*/
enum node_type {
Internal, // an internal node, not related to a port
VertexPort, // a node related to a vertex port
PolygonPort // a node related to a polygon port
};
/**
* @brief The node type
*/
node_type type;
/**
* @brief The location + extension of the node
*/
db::DBox location;
/**
* @brief An index locating the node in the vertex or polygon port lists
*
* For internal nodes, the index is a unique numbers.
*/
unsigned int port_index;
/**
* @brief An index locating the node in a layer
*
* For internal nodes, the layer is 0.
*/
unsigned int layer;
/**
* @brief Gets the R elements connected to this node
*/
const std::list<const RElement *> &elements () const
{
return m_elements;
}
/**
* @brief Returns a string representation of the node
*/
std::string to_string (bool with_coords = false) const;
/**
* @brief Gets the network the node lives in
*/
RNetwork *graph () const
{
return mp_network;
}
protected:
friend class RNetwork;
friend class RElement;
friend class tl::list_impl<RNode, false>;
RNode (RNetwork *network, node_type _type, const db::DBox &_location, unsigned int _port_index, unsigned int _layer)
: type (_type), location (_location), port_index (_port_index), layer (_layer), mp_network (network)
{ }
~RNode () { }
private:
RNode (const RNode &other);
RNode &operator= (const RNode &other);
RNetwork *mp_network;
mutable std::list<const RElement *> m_elements;
};
/**
* @brief Represents an R element in the graph (an edge)
*
* An element has two nodes that form the ends of the edge and
* a conductance value (given in Siemens).
*
* The value can be RElement::short_value() indicating
* "infinite" conductance (a short).
*
* RElement objects cannot be created directly. Use "create_element"
* from RNetwork.
*/
class PEX_PUBLIC RElement
: public tl::list_node<RElement>
{
public:
/**
* @brief The conductance value
*/
double conductance;
/**
* @brief The nodes the resistor connects
*/
const RNode *a () const { return mp_a; }
const RNode *b () const { return mp_b; }
/**
* @brief Gets the other node for n
*/
const RNode *other (const RNode *n) const
{
if (mp_a == n) {
return mp_b;
} else if (mp_b == n) {
return mp_a;
}
tl_assert (false);
}
/**
* @brief Represents the conductance value for a short
*/
static double short_value ()
{
return std::numeric_limits<double>::infinity ();
}
/**
* @brief Gets the resistance value
*
* The resistance value is the inverse of the conducance.
*/
double resistance () const
{
return conductance == short_value () ? 0.0 : 1.0 / conductance;
}
/**
* @brief Returns a string representation of the element
*/
std::string to_string (bool with_coords = false) const;
/**
* @brief Gets the network the node lives in
*/
RNetwork *graph () const
{
return mp_network;
}
protected:
friend class RNetwork;
friend class tl::list_impl<RElement, false>;
RElement (RNetwork *network, double _conductivity, const RNode *a, const RNode *b)
: conductance (_conductivity), mp_network (network), mp_a (a), mp_b (b)
{ }
~RElement ()
{
if (mp_a) {
mp_a->m_elements.erase (m_ia);
}
if (mp_b) {
mp_b->m_elements.erase (m_ib);
}
mp_a = mp_b = 0;
}
std::list<const RElement *>::iterator m_ia, m_ib;
RNetwork *mp_network;
const RNode *mp_a, *mp_b;
private:
RElement (const RElement &other);
RElement &operator= (const RElement &other);
};
/**
* @brief Represents a R network (a graph of RNode and RElement)
*/
class PEX_PUBLIC RNetwork
: public tl::Object
{
public:
typedef tl::list<RNode, false> node_list;
typedef node_list::const_iterator node_iterator;
typedef tl::list<RElement, false> element_list;
typedef element_list::const_iterator element_iterator;
/**
* @brief Constructor
*/
RNetwork ();
/**
* @brief Destructor
*/
~RNetwork ();
/**
* @brief Creates a node with the given type and port index
*
* If the node type is Internal, a new node is created always.
* If the node type is VertexPort or PolygonPort, an existing
* node is returned if one way created with the same type
* or port index already. This avoids creating duplicates
* for the same port.
*/
RNode *create_node (RNode::node_type type, unsigned int port_index, unsigned int layer);
/**
* @brief Creates a new element between the given nodes
*
* If an element already exists between the specified nodes, the
* given value is added to the existing element and the existing
* object is returned.
*/
RElement *create_element (double conductance, RNode *a, RNode *b);
/**
* @brief Removes the given element
*
* Removing the element will also remove any orphan nodes
* at the ends if they are of type Internal.
*/
void remove_element (RElement *element);
/**
* @brief Removes the node and the attached elements.
*
* Only nodes of type Internal can be removed.
*/
void remove_node (RNode *node);
/**
* @brief Clears the network
*/
void clear ();
/**
* @brief Simplifies the network
*
* This will:
* - Join serial resistors if connected by an internal node
* - Remove shorts and join the nodes, if one of them is
* an internal node. The non-internal node will persist.
* - Remove "dangling" resistors if the dangling node is
* an internal one
*/
void simplify ();
/**
* @brief Iterate the nodes (begin)
*/
node_iterator begin_nodes () const
{
return m_nodes.begin ();
}
/**
* @brief Iterate the nodes (end)
*/
node_iterator end_nodes () const
{
return m_nodes.end ();
}
/**
* @brief Gets the number of nodes
*/
size_t num_nodes () const
{
return m_nodes.size ();
}
/**
* @brief Gets the number of internal nodes
*/
size_t num_internal_nodes () const
{
size_t count = 0;
for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) {
if (n->type == pex::RNode::Internal) {
++count;
}
}
return count;
}
/**
* @brief Iterate the elements (begin)
*/
element_iterator begin_elements () const
{
return m_elements.begin ();
}
/**
* @brief Iterate the elements (end)
*/
element_iterator end_elements () const
{
return m_elements.end ();
}
/**
* @brief Gets the number of elements
*/
size_t num_elements () const
{
return m_elements.size ();
}
/**
* @brief Returns a string representation of the graph
*/
std::string to_string (bool with_coords = false) const;
private:
node_list m_nodes;
element_list m_elements;
std::map<std::pair<RNode *, RNode *>, RElement *> m_elements_by_nodes;
std::map<std::pair<RNode::node_type, std::pair<unsigned int, unsigned int> >, RNode *> m_nodes_by_type;
RNetwork (const RNetwork &);
RNetwork &operator= (const RNetwork &);
void join_nodes (RNode *a, RNode *b);
};
}
#endif

View File

@ -0,0 +1,308 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexSquareCountingRExtractor.h"
#include "dbBoxScanner.h"
#include "dbPolygonTools.h"
#include "tlIntervalMap.h"
namespace pex
{
// Value used for number of squares for width 0 (should not happen)
const double infinite_squares = 1e10;
namespace
{
class PolygonPortInteractionReceiver
: public db::box_scanner_receiver2<const db::Polygon, size_t, const db::Polygon, size_t>
{
public:
void add (const db::Polygon *obj1, const size_t &index1, const db::Polygon *obj2, const size_t &index2)
{
if (db::interact_pp (*obj1, *obj2)) {
m_interactions[index1].insert (index2);
}
}
const std::set<size_t> &interactions (size_t index) const
{
static std::set<size_t> empty;
auto i = m_interactions.find (index);
if (i == m_interactions.end ()) {
return empty;
} else {
return i->second;
}
}
private:
std::map<size_t, std::set<size_t> > m_interactions;
};
struct JoinEdgeSets
{
void operator() (std::set<db::Edge> &a, const std::set<db::Edge> &b) const
{
a.insert (b.begin (), b.end ());
}
};
}
SquareCountingRExtractor::SquareCountingRExtractor (double dbu)
{
m_dbu = dbu;
m_skip_simplify = false;
m_decomp_param.split_edges = true;
m_decomp_param.with_segments = false;
}
static
double yatx (const db::Edge &e, int x)
{
db::Point p1 = e.p1 (), p2 = e.p2 ();
if (p1.x () > p2.x ()) {
std::swap (p1, p2);
}
return p1.y () + double (p2.y () - p1.y ()) * double (x - p1.x ()) / double (p2.x () - p1.x ());
}
static
double calculate_squares (db::Coord x1, db::Coord x2, const std::set<db::Edge> &edges)
{
tl_assert (edges.size () == 2);
auto i = edges.begin ();
db::Edge e1 = *i++;
db::Edge e2 = *i;
double w1 = fabs (yatx (e1, x1) - yatx (e2, x1));
double w2 = fabs (yatx (e1, x2) - yatx (e2, x2));
// integrate the resistance along the axis x1->x2 with w=w1->w2
if (w1 < db::epsilon) {
return infinite_squares;
} else if (fabs (w1 - w2) < db::epsilon) {
return (x2 - x1) / w1;
} else {
return (x2 - x1) / (w2 - w1) * log (w2 / w1);
}
}
void
SquareCountingRExtractor::do_extract (const db::Polygon &db_poly, const std::vector<std::pair<PortDefinition, pex::RNode *> > &ports, pex::RNetwork &rnetwork)
{
// "trans" will orient the polygon to be flat rather than tall
db::Trans trans;
if (db_poly.box ().width () < db_poly.box ().height ()) {
trans = db::Trans (db::Trans::r90);
}
// sort the edges into an interval map - as the polygons are convex, there
// can only be two edges in each interval.
tl::interval_map<db::Coord, std::set<db::Edge> > edges;
for (auto e = db_poly.begin_edge (); ! e.at_end (); ++e) {
db::Edge et = trans * *e;
if (et.x1 () != et.x2 ()) {
std::set<db::Edge> es;
es.insert (et);
JoinEdgeSets jes;
edges.add (std::min (et.p1 ().x (), et.p2 ().x ()), std::max (et.p1 ().x (), et.p2 ().x ()), es, jes);
}
}
// sort the port locations - note that we take the port box centers for the location!
std::multimap<db::Coord, pex::RNode *> port_locations;
for (auto p = ports.begin (); p != ports.end (); ++p) {
db::Coord c = (trans * p->first.location).center ().x ();
port_locations.insert (std::make_pair (c, p->second));
}
// walk along the long axis of the polygon and compute the square count between the port locations
for (auto pl = port_locations.begin (); pl != port_locations.end (); ++pl) {
auto pl_next = pl;
++pl_next;
if (pl_next == port_locations.end ()) {
break;
}
db::Coord c = pl->first;
db::Coord cc = pl_next->first;
double r = 0.0;
auto em = edges.find (c);
while (em != edges.end () && em->first.first < cc) {
r += calculate_squares (std::max (c, em->first.first), std::min (cc, em->first.second), em->second);
++em;
}
// TODO: width dependency?
if (r == 0) {
rnetwork.create_element (pex::RElement::short_value (), pl->second, pl_next->second);
} else {
rnetwork.create_element (1.0 / r, pl->second, pl_next->second);
}
}
}
void
SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector<db::Point> &vertex_ports, const std::vector<db::Polygon> &polygon_ports, pex::RNetwork &rnetwork)
{
rnetwork.clear ();
db::CplxTrans to_um (m_dbu);
db::CplxTrans trans = to_um * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ()));
auto inv_trans = trans.inverted ();
db::plc::Graph plc;
db::plc::ConvexDecomposition decomp (&plc);
decomp.decompose (polygon, vertex_ports, m_decomp_param, trans);
// Set up a scanner to detect interactions between polygon ports
// and decomposed polygons
db::box_scanner2<const db::Polygon, size_t, const db::Polygon, size_t> scanner;
std::vector<std::pair<db::Polygon, const db::plc::Polygon *> > decomp_polygons;
for (auto p = plc.begin (); p != plc.end (); ++p) {
decomp_polygons.push_back (std::make_pair (db::Polygon (), p.operator-> ()));
decomp_polygons.back ().first = inv_trans * p->polygon ();
}
for (auto i = decomp_polygons.begin (); i != decomp_polygons.end (); ++i) {
scanner.insert1 (&i->first, i - decomp_polygons.begin ());
}
for (auto i = polygon_ports.begin (); i != polygon_ports.end (); ++i) {
scanner.insert2 (i.operator-> (), i - polygon_ports.begin ());
}
PolygonPortInteractionReceiver interactions;
db::box_convert<db::Polygon> bc;
scanner.process (interactions, 1, bc, bc);
// Generate the internal ports: those are defined by edges connecting two polygons
std::vector<const db::plc::Edge *> internal_port_edges;
std::map<const db::plc::Edge *, size_t> internal_ports;
std::vector<std::vector<size_t> > internal_port_indexes;
for (auto i = decomp_polygons.begin (); i != decomp_polygons.end (); ++i) {
internal_port_indexes.push_back (std::vector<size_t> ());
auto p = i->second;
for (size_t j = 0; j < p->size (); ++j) {
const db::plc::Edge *e = p->edge (int (j));
if (e->left () && e->right ()) {
auto ip = internal_ports.find (e);
if (ip == internal_ports.end ()) {
size_t n = internal_port_edges.size ();
internal_port_edges.push_back (e);
ip = internal_ports.insert (std::make_pair (e, n)).first;
}
internal_port_indexes.back ().push_back (ip->second);
}
}
}
// Now we can extract the resistors
std::vector<std::pair<PortDefinition, pex::RNode *> > ports;
std::map<PortDefinition, pex::RNode *> nodes_for_ports;
for (auto p = decomp_polygons.begin (); p != decomp_polygons.end (); ++p) {
ports.clear ();
const db::Polygon &db_poly = p->first;
const db::plc::Polygon *plc_poly = p->second;
const std::set<size_t> &pp_indexes = interactions.interactions (p - decomp_polygons.begin ());
const std::vector<size_t> &ip_indexes = internal_port_indexes [p - decomp_polygons.begin ()];
// set up the ports:
// 1. internal ports
for (auto i = ip_indexes.begin (); i != ip_indexes.end (); ++i) {
db::Box loc = (inv_trans * internal_port_edges [*i]->edge ()).bbox ();
ports.push_back (std::make_pair (PortDefinition (pex::RNode::Internal, loc, (unsigned int) *i), (pex::RNode *) 0));
}
// 2. vertex ports
for (size_t i = 0; i < plc_poly->internal_vertexes (); ++i) {
auto v = plc_poly->internal_vertex (i);
db::Point loc = inv_trans * *v;
for (auto pi = v->ids ().begin (); pi != v->ids ().end (); ++pi) {
ports.push_back (std::make_pair (PortDefinition (pex::RNode::VertexPort, loc, *pi), (pex::RNode *) 0));
}
}
// 3. polygon ports
// (NOTE: here we only take the center of the bounding box)
for (auto i = pp_indexes.begin (); i != pp_indexes.end (); ++i) {
db::Box loc = polygon_ports [*i].box ();
ports.push_back (std::make_pair (PortDefinition (pex::RNode::PolygonPort, loc, (unsigned int) *i), (pex::RNode *) 0));
}
// create nodes for the ports
// (we reuse nodes for existing ports in "nodes_for_ports", hence to establish the connection)
for (auto p = ports.begin (); p != ports.end (); ++p) {
auto n4p = nodes_for_ports.find (p->first);
if (n4p == nodes_for_ports.end ()) {
pex::RNode *node = rnetwork.create_node (p->first.type, p->first.port_index, 0);
node->location = to_um * p->first.location;
n4p = nodes_for_ports.insert (std::make_pair (p->first, node)).first;
}
p->second = n4p->second;
}
do_extract (db_poly, ports, rnetwork);
}
if (! m_skip_simplify) {
rnetwork.simplify ();
}
}
}

View File

@ -0,0 +1,155 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_pexSquareCountingRExtractor
#define HDR_pexSquareCountingRExtractor
#include "pexCommon.h"
#include "pexRNetwork.h"
#include "pexRExtractor.h"
#include "dbPLCConvexDecomposition.h"
namespace pex
{
/**
* @brief The Square Counting R Extractor
*
* The idea of that extractor is to first decompose the polygon into
* convex parts. Each convex part is taken as "thin" and the current
* flow being parallel and homogeneous to the long axis.
*
* Internal ports are created between the partial polygons where
* they touch.
*
* The ports are considered point-like (polygon ports are replaced
* by points in their bounding box centers) and inject current
* at their specific position only. The resistance is accumulated
* between ports by integrating the squares (length along
* the long axis / width).
*/
class PEX_PUBLIC SquareCountingRExtractor
: public RExtractor
{
public:
/**
* @brief The constructor
*/
SquareCountingRExtractor (double dbu);
/**
* @brief Gets the decomposition parameters
*/
db::plc::ConvexDecompositionParameters &decomposition_parameters ()
{
return m_decomp_param;
}
/**
* @brief Sets a value indicating whether to skip the simplify step
*/
void set_skip_simplfy (bool f)
{
m_skip_simplify = f;
}
/**
* @brief Gets a value indicating whether to skip the simplify step
*/
bool skip_simplify () const
{
return m_skip_simplify;
}
/**
* @brief Sets the database unit
*/
void set_dbu (double dbu)
{
m_dbu = dbu;
}
/**
* @brief Gets the database unit
*/
double dbu () const
{
return m_dbu;
}
/**
* @brief Implementation of the extraction function
*/
virtual void extract (const db::Polygon &polygon, const std::vector<db::Point> &vertex_ports, const std::vector<db::Polygon> &polygon_ports, RNetwork &rnetwork);
protected:
/**
* @brief A helper structure defining a port
*/
struct PortDefinition
{
PortDefinition ()
: type (pex::RNode::Internal), port_index (0)
{ }
PortDefinition (pex::RNode::node_type _type, const db::Point &_location, unsigned int _port_index)
: type (_type), location (_location, _location), port_index (_port_index)
{ }
PortDefinition (pex::RNode::node_type _type, const db::Box &_location, unsigned int _port_index)
: type (_type), location (_location), port_index (_port_index)
{ }
bool operator< (const PortDefinition &other) const
{
if (type != other.type) {
return type < other.type;
}
if (port_index != other.port_index) {
return port_index < other.port_index;
}
return false;
}
bool operator== (const PortDefinition &other) const
{
return type == other.type && port_index == other.port_index;
}
pex::RNode::node_type type;
db::Box location;
unsigned int port_index;
};
void do_extract (const db::Polygon &db_poly, const std::vector<std::pair<PortDefinition, pex::RNode *> > &ports, pex::RNetwork &rnetwork);
private:
db::plc::ConvexDecompositionParameters m_decomp_param;
double m_dbu;
bool m_skip_simplify;
};
}
#endif

View File

@ -0,0 +1,344 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexTriangulationRExtractor.h"
#include "dbBoxScanner.h"
#include "dbPolygonTools.h"
#include "tlIntervalMap.h"
namespace pex
{
TriangulationRExtractor::TriangulationRExtractor (double dbu)
{
m_dbu = dbu;
m_skip_reduction = false;
m_tri_param.min_b = 0.3;
m_tri_param.max_area = 0.0;
}
void
TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector<db::Point> &vertex_ports, const std::vector<db::Polygon> &polygon_ports, pex::RNetwork &rnetwork)
{
rnetwork.clear ();
tl::SelfTimer timer (tl::verbosity () >= m_tri_param.base_verbosity + 1, "Extracting resistor network from polygon (TriangulationRExtractor)");
db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ()));
db::plc::Graph plc;
db::plc::Triangulation tri (&plc);
std::unordered_map <const db::plc::Vertex *, size_t> pp_vertexes;
if (polygon_ports.empty ()) {
tri.triangulate (polygon, vertex_ports, m_tri_param, trans);
plc.dump ("debug.gds");
} else {
tl::SelfTimer timer_tri (tl::verbosity () >= m_tri_param.base_verbosity + 11, "Triangulation step");
// Subtract the polygon ports from the original polygon and compute the intersection.
// Hence we have coincident edges that we can use to identify the nodes that are
// connected for the polygon ports
db::Region org (polygon);
db::Region pp (polygon_ports.begin (), polygon_ports.end ());
db::Region residual_poly = org - pp;
// We must not remove outside triangles yet, as we need them for "find_vertexes_along_line"
db::plc::TriangulationParameters param = m_tri_param;
param.remove_outside_triangles = false;
tri.clear ();
std::vector<std::vector<db::plc::Vertex *> > edge_contours;
// first step of the triangulation
for (auto p = residual_poly.begin_merged (); ! p.at_end (); ++p) {
tri.make_contours (*p, trans, edge_contours);
}
unsigned int id = 0;
for (auto v = vertex_ports.begin (); v != vertex_ports.end (); ++v) {
tri.insert_point (trans * *v)->set_is_precious (true, id++);
}
for (auto p = polygon_ports.begin (); p != polygon_ports.end (); ++p) {
// create vertexes for the port polygon vertexes - this ensures we will find vertexes
// on the edges of the polygons - yet, they may be outside of the original polygon.
// In that case they will not be considered
for (auto e = p->begin_edge (); !e.at_end (); ++e) {
tri.insert_point (trans * (*e).p1 ())->set_is_precious (true, id);
}
}
// constrain and refine the triangulation
tri.constrain (edge_contours);
tri.refine (param);
// identify the vertexes present for the polygon port -> store them inside pp_vertexes
for (auto p = polygon_ports.begin (); p != polygon_ports.end (); ++p) {
for (auto e = p->begin_edge (); !e.at_end (); ++e) {
// NOTE: this currently only works if one of the end points is an actual
// vertex.
auto vport = tri.find_vertexes_along_line (trans * (*e).p1 (), trans * (*e).p2 ());
for (auto v = vport.begin (); v != vport.end (); ++v) {
pp_vertexes.insert (std::make_pair (*v, p - polygon_ports.begin ()));
}
}
}
tri.remove_outside_triangles ();
}
// Create a network node for each triangle node.
std::unordered_map<const db::plc::Vertex *, pex::RNode *> vertex2node;
std::unordered_set<size_t> vports_present;
std::map<size_t, pex::RNode *> pport_nodes;
size_t internal_node_id = 0;
for (auto p = plc.begin (); p != plc.end (); ++p) {
for (size_t iv = 0; iv < p->size (); ++iv) {
const db::plc::Vertex *vertex = p->vertex (int (iv));
if (vertex2node.find (vertex) != vertex2node.end ()) {
continue;
}
pex::RNode *n = 0;
auto ipp = pp_vertexes.find (vertex);
if (ipp != pp_vertexes.end ()) {
size_t port_index = ipp->second;
auto pn = pport_nodes.find (port_index);
if (pn != pport_nodes.end ()) {
n = pn->second;
} else {
n = rnetwork.create_node (pex::RNode::PolygonPort, (unsigned int) port_index, 0);
pport_nodes.insert (std::make_pair (port_index, n));
n->location = trans * polygon_ports [port_index].box ();
}
} else if (vertex->is_precious ()) {
for (auto pi = vertex->ids ().begin (); pi != vertex->ids ().end (); ++pi) {
size_t port_index = size_t (*pi);
if (port_index < vertex_ports.size ()) {
RNode *nn = rnetwork.create_node (pex::RNode::VertexPort, (unsigned int) port_index, 0);
nn->location = db::DBox (*vertex, *vertex);
if (n) {
// in case of multiple vertexes on the same spot, short them
rnetwork.create_element (RElement::short_value (), n, nn);
} else {
n = nn;
}
vports_present.insert (port_index);
}
}
} else {
n = rnetwork.create_node (pex::RNode::Internal, (unsigned int) internal_node_id++, 0);
n->location = db::DBox (*vertex, *vertex);
}
if (n) {
vertex2node.insert (std::make_pair (vertex, n));
}
}
}
// check for vertex ports not assigned to a node
// -> this may be an indication for a vertex port inside a polygon port
for (size_t iv = 0; iv < vertex_ports.size (); ++iv) {
if (vports_present.find (iv) != vports_present.end ()) {
continue;
}
db::Point vp = vertex_ports [iv];
for (auto p = polygon_ports.begin (); p != polygon_ports.end (); ++p) {
if (p->box ().contains (vp) && db::inside_poly_test<db::Polygon> (*p) (vp) >= 0) {
auto ip = pport_nodes.find (p - polygon_ports.begin ());
if (ip != pport_nodes.end ()) {
// create a new vertex port and short it to the polygon port
auto n = rnetwork.create_node (pex::RNode::VertexPort, (unsigned int) iv, 0);
n->location = db::DBox (trans * vp, trans * vp);
rnetwork.create_element (pex::RElement::short_value (), n, ip->second);
}
}
}
}
// produce the conductances for each triangle
for (auto p = plc.begin (); p != plc.end (); ++p) {
create_conductances (*p, vertex2node, rnetwork);
}
// eliminate internal nodes
if (! m_skip_reduction) {
eliminate_all (rnetwork);
}
}
void
TriangulationRExtractor::create_conductances (const db::plc::Polygon &tri, const std::unordered_map<const db::plc::Vertex *, pex::RNode *> &vertex2node, RNetwork &rnetwork)
{
tl_assert (tri.size () == 3);
for (int i = 0; i < 3; ++i) {
const db::plc::Vertex *pm1 = tri.vertex (i);
const db::plc::Vertex *p0 = tri.vertex (i + 1);
const db::plc::Vertex *p1 = tri.vertex (i + 2);
auto i0 = vertex2node.find (p0);
auto im1 = vertex2node.find (pm1);
if (i0->second != im1->second) {
double a = fabs (db::vprod (*pm1 - *p0, *p1 - *p0) * 0.5);
double lm1 = (*p0 - *pm1).sq_length ();
double l0 = (*p1 - *p0).sq_length ();
double l1 = (*pm1 - *p1).sq_length ();
double s = (l0 + l1 - lm1) / (8.0 * a);
rnetwork.create_element (s, i0->second, im1->second);
}
}
}
void
TriangulationRExtractor::eliminate_all (RNetwork &rnetwork)
{
if (tl::verbosity () >= m_tri_param.base_verbosity + 10) {
tl::info << "Starting elimination with " << rnetwork.num_internal_nodes () << " internal nodes and " << rnetwork.num_elements () << " resistors";
}
unsigned int niter = 0;
std::vector<pex::RNode *> to_eliminate;
size_t nmax = 3;
while (nmax > 0) {
bool another_loop = true;
while (another_loop) {
size_t nmax_next = 0;
to_eliminate.clear ();
for (auto n = rnetwork.begin_nodes (); n != rnetwork.end_nodes (); ++n) {
if (n->type == pex::RNode::Internal) {
size_t nn = n->elements ().size ();
if (nn <= nmax) {
to_eliminate.push_back (const_cast<pex::RNode *> (n.operator-> ()));
} else if (nmax_next == 0 || nn < nmax_next) {
nmax_next = nn;
}
}
}
if (to_eliminate.empty ()) {
another_loop = false;
nmax = nmax_next;
if (tl::verbosity () >= m_tri_param.base_verbosity + 10) {
tl::info << "Nothing left to eliminate with nmax=" << nmax;
}
} else {
for (auto n = to_eliminate.begin (); n != to_eliminate.end (); ++n) {
eliminate_node (*n, rnetwork);
}
niter += 1;
if (tl::verbosity () >= m_tri_param.base_verbosity + 10) {
tl::info << "Nodes left after iteration " << niter << " with nmax=" << nmax << ": " << rnetwork.num_internal_nodes () << " with " << rnetwork.num_elements () << " edges.";
}
}
}
}
}
void
TriangulationRExtractor::eliminate_node (pex::RNode *node, RNetwork &rnetwork)
{
double s_sum = 0.0;
for (auto e = node->elements ().begin (); e != node->elements ().end (); ++e) {
s_sum += (*e)->conductance;
}
if (fabs (s_sum) > 1e-10) {
for (auto e = node->elements ().begin (); e != node->elements ().end (); ++e) {
auto ee = e;
++ee;
for ( ; ee != node->elements ().end (); ++ee) {
pex::RNode *n1 = const_cast <pex::RNode *> ((*e)->other (node));
pex::RNode *n2 = const_cast <pex::RNode *> ((*ee)->other (node));
double c = (*e)->conductance * (*ee)->conductance / s_sum;
rnetwork.create_element (c, n1, n2);
}
}
}
rnetwork.remove_node (node);
}
}

View File

@ -0,0 +1,119 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_pexTriangulationRExtractor
#define HDR_pexTriangulationRExtractor
#include "pexCommon.h"
#include "pexRNetwork.h"
#include "pexRExtractor.h"
#include "dbPLCTriangulation.h"
namespace pex
{
/**
* @brief An R extractor based on a triangulation of the resistor area
*
* This resistor extractor starts with a triangulation of the
* polygon area and substitutes each triangle by a 3-resistor network.
*
* After this, it will eliminate nodes where possible.
*
* This extractor delivers a resistor matrix (there is a resistor
* between every specified port).
*
* Polygon ports are considered to be perfectly conductive and cover
* their given area, shorting all nodes at their boundary.
*
* This extractor delivers higher quality results than the square
* counting extractor, but is slower in general.
*/
class PEX_PUBLIC TriangulationRExtractor
: public RExtractor
{
public:
/**
* @brief The constructor
*/
TriangulationRExtractor (double dbu);
/**
* @brief Gets the triangulation parameters
*/
db::plc::TriangulationParameters &triangulation_parameters ()
{
return m_tri_param;
}
/**
* @brief Sets a value indicating whether to skip the reduction step
*/
void set_skip_reduction (bool f)
{
m_skip_reduction = f;
}
/**
* @brief Gets a value indicating whether to skip the reduction step
*/
bool skip_reduction () const
{
return m_skip_reduction;
}
/**
* @brief Sets the database unit
*/
void set_dbu (double dbu)
{
m_dbu = dbu;
}
/**
* @brief Gets the database unit
*/
double dbu () const
{
return m_dbu;
}
/**
* @brief Implementation of the extraction function
*/
virtual void extract (const db::Polygon &polygon, const std::vector<db::Point> &vertex_ports, const std::vector<db::Polygon> &polygon_ports, RNetwork &rnetwork);
private:
db::plc::TriangulationParameters m_tri_param;
double m_dbu;
bool m_skip_reduction;
void create_conductances (const db::plc::Polygon &tri, const std::unordered_map<const db::plc::Vertex *, RNode *> &vertex2node, RNetwork &rnetwork);
void eliminate_node (pex::RNode *node, RNetwork &rnetwork);
void eliminate_all (RNetwork &rnetwork);
};
}
#endif

View File

@ -0,0 +1,256 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexRExtractor.h"
#include "pexRNetwork.h"
#include "tlUnitTest.h"
TEST(network_basic)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::Internal, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 1, 1);
EXPECT_EQ (n1 != n2, true);
pex::RNode *n2_dup = rn.create_node (pex::RNode::Internal, 1, 1);
EXPECT_EQ (n2 != n2_dup, true);
/* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2);
EXPECT_EQ (rn.to_string (),
"R $1 $1.1 2"
);
}
TEST(network_basic_vertex_nodes)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::VertexPort, 1, 1);
EXPECT_EQ (n1 != n2, true);
pex::RNode *n2_dup = rn.create_node (pex::RNode::VertexPort, 1, 1);
EXPECT_EQ (n2 == n2_dup, true);
pex::RNode *n2_wrong_type = rn.create_node (pex::RNode::PolygonPort, 1, 1);
EXPECT_EQ (n2 != n2_wrong_type, true);
/* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2);
EXPECT_EQ (rn.to_string (),
"R V1 V1.1 2"
);
}
TEST(network_basic_polygon_nodes)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::PolygonPort, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::PolygonPort, 1, 1);
EXPECT_EQ (n1 != n2, true);
pex::RNode *n2_dup = rn.create_node (pex::RNode::PolygonPort, 1, 1);
EXPECT_EQ (n2 == n2_dup, true);
pex::RNode *n2_wrong_type = rn.create_node (pex::RNode::VertexPort, 1, 1);
EXPECT_EQ (n2 != n2_wrong_type, true);
/* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2);
EXPECT_EQ (rn.to_string (),
"R P1 P1.1 2"
);
}
TEST(network_basic_elements)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::Internal, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0);
/* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2);
EXPECT_EQ (rn.to_string (),
"R $1 $2 2"
);
pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0);
/* pex::RElement *e13 = */ rn.create_element (0.25, n1, n3);
pex::RElement *e23 = rn.create_element (1.0, n2, n3);
EXPECT_EQ (rn.to_string (),
"R $1 $2 2\n"
"R $1 $3 4\n"
"R $2 $3 1"
);
pex::RElement *e23b = rn.create_element (4.0, n2, n3);
EXPECT_EQ (e23 == e23b, true);
EXPECT_EQ (rn.to_string (),
"R $1 $2 2\n"
"R $1 $3 4\n"
"R $2 $3 0.2"
);
pex::RElement *e23c = rn.create_element (5.0, n3, n2);
EXPECT_EQ (e23 == e23c, true);
EXPECT_EQ (rn.to_string (),
"R $1 $2 2\n"
"R $1 $3 4\n"
"R $2 $3 0.1"
);
rn.remove_element (e23);
EXPECT_EQ (rn.to_string (),
"R $1 $2 2\n"
"R $1 $3 4"
);
rn.remove_node (n3);
EXPECT_EQ (rn.to_string (),
"R $1 $2 2"
);
rn.clear ();
EXPECT_EQ (rn.to_string (), "");
}
TEST(network_simplify1)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0);
pex::RNode *n3 = rn.create_node (pex::RNode::VertexPort, 3, 0);
rn.create_element (1, n1, n2);
rn.create_element (pex::RElement::short_value (), n2, n3);
rn.create_element (1, n1, n3);
EXPECT_EQ (rn.to_string (),
"R $2 V1 1\n"
"R $2 V3 0\n"
"R V1 V3 1"
);
rn.simplify ();
EXPECT_EQ (rn.to_string (),
"R V1 V3 0.5"
);
}
TEST(network_simplify2)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0);
pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0);
pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4, 0);
pex::RNode *n5 = rn.create_node (pex::RNode::VertexPort, 5, 0);
rn.create_element (1, n1, n2);
rn.create_element (pex::RElement::short_value (), n2, n3);
rn.create_element (1, n3, n4);
rn.create_element (1, n3, n5);
EXPECT_EQ (rn.to_string (),
"R $2 V1 1\n"
"R $2 $3 0\n"
"R $3 V4 1\n"
"R $3 V5 1"
);
rn.simplify ();
EXPECT_EQ (rn.to_string (),
"R $2 V1 1\n"
"R $2 V4 1\n"
"R $2 V5 1"
);
}
TEST(network_simplify3)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0);
pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0);
pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4, 0);
rn.create_element (1, n1, n2);
rn.create_element (pex::RElement::short_value (), n2, n3);
rn.create_element (1, n3, n4);
EXPECT_EQ (rn.to_string (),
"R $2 V1 1\n"
"R $2 $3 0\n"
"R $3 V4 1"
);
rn.simplify ();
EXPECT_EQ (rn.to_string (),
"R V1 V4 2"
);
}
TEST(network_simplify4)
{
pex::RNetwork rn;
EXPECT_EQ (rn.to_string (), "");
pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0);
pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0);
pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0);
pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4, 0);
rn.create_element (1, n1, n4);
rn.create_element (1, n2, n1);
rn.create_element (1, n4, n3);
EXPECT_EQ (rn.to_string (),
"R V1 V4 1\n"
"R $2 V1 1\n"
"R $3 V4 1"
);
rn.simplify ();
EXPECT_EQ (rn.to_string (),
"R V1 V4 1"
);
}

View File

@ -0,0 +1,317 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexRNetExtractor.h"
#include "pexRExtractorTech.h"
#include "pexRNetwork.h"
#include "dbReader.h"
#include "dbLayout.h"
#include "tlUnitTest.h"
class TestableRNetExtractor
: public pex::RNetExtractor
{
public:
TestableRNetExtractor (double dbu) : pex::RNetExtractor (dbu) { }
using pex::RNetExtractor::create_via_ports;
};
static std::string network2s (const pex::RNetwork &network)
{
std::vector<std::string> r;
for (auto e = network.begin_elements (); e != network.end_elements (); ++e) {
const pex::RElement &element = *e;
std::string na = (element.a ()->type != pex::RNode::Internal ? element.a ()->to_string () : "") +
element.a ()->location.to_string ();
std::string nb = (element.b ()->type != pex::RNode::Internal ? element.b ()->to_string () : "") +
element.b ()->location.to_string ();
if (nb < na) {
std::swap (na, nb);
}
std::string s = "R " + na + " " + nb + " " + tl::to_string (element.resistance ());
r.push_back (s);
}
std::sort (r.begin (), r.end ());
return tl::join (r, "\n");
}
TEST(basic)
{
unsigned int l1 = 1;
unsigned int l2 = 1;
unsigned int l3 = 1;
pex::RExtractorTech tech;
pex::RExtractorTechVia via1;
via1.bottom_conductor = l1;
via1.cut_layer = l2;
via1.top_conductor = l3;
via1.resistance = 2.0;
via1.merge_distance = 0.2;
tech.vias.push_back (via1);
pex::RExtractorTechConductor cond1;
cond1.layer = l1;
cond1.resistance = 0.5;
tech.conductors.push_back (cond1);
pex::RExtractorTechConductor cond2;
cond2.layer = l3;
cond2.resistance = 0.25;
cond2.algorithm = pex::RExtractorTechConductor::Tesselation;
cond2.triangulation_max_area = 1.5;
cond2.triangulation_min_b = 0.5;
tech.conductors.push_back (cond2);
tech.skip_simplify = true;
EXPECT_EQ (tech.to_string (),
"skip_simplify=true\n"
"Via(bottom=L1, cut=L1, top=L1, R=2 \xC2\xB5m\xC2\xB2*Ohm, d_merge=0.2 \xC2\xB5m)\n"
"Conductor(layer=L1, R=0.5 Ohm/sq, algo=SquareCounting)\n"
"Conductor(layer=L1, R=0.25 Ohm/sq, algo=Tesselation, tri_min_b=0.5 \xC2\xB5m, tri_max_area=1.5 \xC2\xB5m\xC2\xB2)"
);
}
TEST(netex_viagen1)
{
db::Layout ly;
{
std::string fn = tl::testdata () + "/pex/netex_viagen1.gds";
tl::InputStream is (fn);
db::Reader reader (is);
reader.read (ly);
}
TestableRNetExtractor rex (ly.dbu ());
auto tc = ly.cell_by_name ("TOP");
tl_assert (tc.first);
unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0));
unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0));
unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0));
std::map<unsigned int, db::Region> geo;
geo.insert (std::make_pair (l2, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l2))));
pex::RNetwork network;
pex::RExtractorTech tech;
pex::RExtractorTechVia via1;
via1.bottom_conductor = l1;
via1.cut_layer = l2;
via1.top_conductor = l3;
via1.resistance = 2.0;
tech.vias.push_back (via1);
std::map<unsigned int, std::vector<pex::RNetExtractor::ViaPort> > via_ports;
rex.create_via_ports (tech, geo, via_ports, network);
EXPECT_EQ (via_ports [l1].size (), size_t (4));
EXPECT_EQ (via_ports [l2].size (), size_t (0));
EXPECT_EQ (via_ports [l3].size (), size_t (4));
EXPECT_EQ (network2s (network),
"R (0.4,0.5;0.6,0.7) (0.4,0.5;0.6,0.7) 50\n"
"R (0.8,0.5;1,0.7) (0.8,0.5;1,0.7) 50\n"
"R (1.7,0.1;1.9,0.3) (1.7,0.1;1.9,0.3) 50\n"
"R (2.9,0.5;3.1,0.7) (2.9,0.5;3.1,0.7) 50"
);
}
TEST(netex_viagen2)
{
db::Layout ly;
{
std::string fn = tl::testdata () + "/pex/netex_viagen2.gds";
tl::InputStream is (fn);
db::Reader reader (is);
reader.read (ly);
}
TestableRNetExtractor rex (ly.dbu ());
auto tc = ly.cell_by_name ("TOP");
tl_assert (tc.first);
unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0));
unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0));
unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0));
std::map<unsigned int, db::Region> geo;
geo.insert (std::make_pair (l2, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l2))));
pex::RNetwork network;
pex::RExtractorTech tech;
pex::RExtractorTechVia via1;
via1.bottom_conductor = l1;
via1.cut_layer = l2;
via1.top_conductor = l3;
via1.resistance = 2.0;
via1.merge_distance = 0.2;
tech.vias.push_back (via1);
std::map<unsigned int, std::vector<pex::RNetExtractor::ViaPort> > via_ports;
rex.create_via_ports (tech, geo, via_ports, network);
EXPECT_EQ (via_ports [l1].size (), size_t (6));
EXPECT_EQ (via_ports [l2].size (), size_t (0));
EXPECT_EQ (via_ports [l3].size (), size_t (6));
EXPECT_EQ (network2s (network),
"R (0.4,0.4;2.2,4.2) (0.4,0.4;2.2,4.2) 1\n"
"R (0.6,4.9;1.2,5.1) (0.6,4.9;1.2,5.1) 25\n"
"R (2.2,1.2;3.4,3.4) (2.2,1.2;3.4,3.4) 2.77777777778\n"
"R (2.5,3.7;2.7,3.9) (2.5,3.7;2.7,3.9) 50\n"
"R (3,3.7;3.2,3.9) (3,3.7;3.2,3.9) 50\n"
"R (4.6,2.8;4.8,3) (4.6,2.8;4.8,3) 50"
);
}
TEST(netex_2layer)
{
db::Layout ly;
{
std::string fn = tl::testdata () + "/pex/netex_test1.gds";
tl::InputStream is (fn);
db::Reader reader (is);
reader.read (ly);
}
TestableRNetExtractor rex (ly.dbu ());
auto tc = ly.cell_by_name ("TOP");
tl_assert (tc.first);
unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0));
unsigned int l1p = ly.get_layer (db::LayerProperties (1, 1));
unsigned int l1v = ly.get_layer (db::LayerProperties (1, 2));
unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0));
unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0));
unsigned int l3p = ly.get_layer (db::LayerProperties (3, 1));
unsigned int l3v = ly.get_layer (db::LayerProperties (3, 2));
// That is coincidence, but it needs to be that way for the strings to match
EXPECT_EQ (l1, 1u);
EXPECT_EQ (l2, 0u);
EXPECT_EQ (l3, 2u);
std::map<unsigned int, db::Region> geo;
geo.insert (std::make_pair (l1, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l1))));
geo.insert (std::make_pair (l2, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l2))));
geo.insert (std::make_pair (l3, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l3))));
pex::RNetwork network;
pex::RExtractorTech tech;
tech.skip_simplify = true;
pex::RExtractorTechVia via1;
via1.bottom_conductor = l1;
via1.cut_layer = l2;
via1.top_conductor = l3;
via1.resistance = 2.0;
via1.merge_distance = 0.2;
tech.vias.push_back (via1);
pex::RExtractorTechConductor cond1;
cond1.layer = l1;
cond1.resistance = 0.5;
tech.conductors.push_back (cond1);
pex::RExtractorTechConductor cond2;
cond2.layer = l3;
cond2.resistance = 0.25;
tech.conductors.push_back (cond2);
std::map<unsigned int, std::vector<db::Point> > vertex_ports;
std::map<unsigned int, std::vector<db::Polygon> > polygon_ports;
db::Region l1p_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l1p));
for (auto p = l1p_region.begin_merged (); ! p.at_end (); ++p) {
polygon_ports[l1].push_back (*p);
}
db::Region l3p_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l3p));
for (auto p = l3p_region.begin_merged (); ! p.at_end (); ++p) {
polygon_ports[l3].push_back (*p);
}
db::Region l1v_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l1v));
for (auto p = l1v_region.begin_merged (); ! p.at_end (); ++p) {
vertex_ports[l1].push_back (p->box ().center ());
}
db::Region l3v_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l3v));
for (auto p = l3v_region.begin_merged (); ! p.at_end (); ++p) {
vertex_ports[l3].push_back (p->box ().center ());
}
rex.extract (tech, geo, vertex_ports, polygon_ports, network);
EXPECT_EQ (network2s (network),
"R (0.1,0.1;0.7,0.7) (0.1,0.1;0.7,0.7) 12.5\n"
"R (0.1,0.1;0.7,0.7) V0.1(5.2,0.4;5.2,0.4) 3\n"
"R (0.1,0.1;0.7,0.7) V0.2(0.4,-5.6;0.4,-5.6) 1.875\n"
"R (0.3,-5.7;0.5,-5.5) (0.3,-5.7;0.5,-5.5) 50\n"
"R (0.3,-5.7;0.5,-5.5) (9.3,-5.9;9.9,-5.3) 5.75\n"
"R (0.3,-5.7;0.5,-5.5) V0.2(0.4,-5.6;0.4,-5.6) 0\n"
"R (10,-3.5;10,-2.7) (9.3,-5.9;9.9,-5.3) 0.78125\n"
"R (10,-3.5;10,-2.7) (9.3,0.1;9.9,0.3) 1.03125\n"
"R (10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n"
"R (9.3,-5.9;9.9,-5.3) (9.3,-5.9;9.9,-5.3) 12.5\n"
"R (9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n"
"R (9.3,0.1;9.9,0.3) (9.3,0.1;9.9,0.3) 25\n"
"R (9.3,0.1;9.9,0.3) V0.1(5.2,0.4;5.2,0.4) 2.75"
);
tech.skip_simplify = false;
rex.extract (tech, geo, vertex_ports, polygon_ports, network);
EXPECT_EQ (network2s (network),
"R (10,-3.5;10,-2.7) (9.3,-5.9;9.9,-5.3) 13.28125\n"
"R (10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n"
"R (10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.78125\n"
"R (9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n"
"R (9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75\n"
"R V0.1(5.2,0.4;5.2,0.4) V0.2(0.3,-5.7;0.5,-5.5) 17.375"
);
}

View File

@ -0,0 +1,215 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexSquareCountingRExtractor.h"
#include "tlUnitTest.h"
namespace
{
class TestableSquareCountingRExtractor
: public pex::SquareCountingRExtractor
{
public:
TestableSquareCountingRExtractor ()
: pex::SquareCountingRExtractor (0.001)
{ }
using pex::SquareCountingRExtractor::PortDefinition;
using pex::SquareCountingRExtractor::do_extract;
};
}
static std::string network2s (const pex::RNetwork &network)
{
std::vector<std::string> r;
for (auto e = network.begin_elements (); e != network.end_elements (); ++e) {
const pex::RElement &element = *e;
std::string na = (element.a ()->type != pex::RNode::Internal ? element.a ()->to_string () : "") +
element.a ()->location.to_string ();
std::string nb = (element.b ()->type != pex::RNode::Internal ? element.b ()->to_string () : "") +
element.b ()->location.to_string ();
if (nb < na) {
std::swap (na, nb);
}
std::string s = "R " + na + " " + nb + " " + tl::to_string (element.resistance ());
r.push_back (s);
}
std::sort (r.begin (), r.end ());
return tl::join (r, "\n");
}
TEST(basic)
{
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 100),
db::Point (1000, 1000),
db::Point (2100, 1000),
db::Point (2100, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
TestableSquareCountingRExtractor rex;
pex::RNetwork rn;
TestableSquareCountingRExtractor::PortDefinition pd1 (pex::RNode::Internal, db::Point (-50, 50), 0);
TestableSquareCountingRExtractor::PortDefinition pd2 (pex::RNode::Internal, db::Point (1000, 100), 1);
TestableSquareCountingRExtractor::PortDefinition pd3 (pex::RNode::Internal, db::Point (1000, 500), 2);
TestableSquareCountingRExtractor::PortDefinition pd4 (pex::RNode::Internal, db::Point (2000, 500), 3);
std::vector<TestableSquareCountingRExtractor::PortDefinition> pds;
pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (0, 50), 0));
pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (1000, 100), 1));
pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (1000, 500), 2));
pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (2000, 500), 3));
std::vector<std::pair<TestableSquareCountingRExtractor::PortDefinition, pex::RNode *> > ports;
for (auto pd = pds.begin (); pd != pds.end (); ++pd) {
ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index, 0)));
}
rex.do_extract (poly, ports, rn);
EXPECT_EQ (rn.to_string (),
"R $0 $1 2.55843\n" // w ramp w=100 to 1000 over x=0 to 1000 (squares = (x2-x1)/(w2-w1)*log(w2/w1) by integration)
"R $1 $2 0\n" // transition from y=50 to y=500 parallel to current direction
"R $2 $3 1" // 1 square between x=1000 and 2000 (w=1000)
);
// After rotation
rn.clear ();
db::Trans r90 (db::Trans::r90);
poly.transform (r90);
ports.clear ();
for (auto pd = pds.begin (); pd != pds.end (); ++pd) {
ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index, 0)));
ports.back ().first.location.transform (r90);
}
rex.do_extract (poly, ports, rn);
// Same network, but opposite order. $1 and $2 are shorted, hence can be swapped.
EXPECT_EQ (rn.to_string (),
"R $1 $3 1\n"
"R $1 $2 0\n"
"R $0 $2 2.55843"
);
}
TEST(extraction)
{
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 100),
db::Point (1000, 100),
db::Point (1000, 1000),
db::Point (1100, 1000),
db::Point (1100, 100),
db::Point (1700, 100),
db::Point (1700, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::SquareCountingRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
vertex_ports.push_back (db::Point (0, 50)); // V0
vertex_ports.push_back (db::Point (1650, 50)); // V1
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (db::Polygon (db::Box (1000, 900, 1100, 1000))); // P0
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (network2s (rn),
"R (1,0.1;1.1,0.1) P0(1,0.9;1.1,1) 8.5\n"
"R (1,0.1;1.1,0.1) V0(0,0.05;0,0.05) 10.5\n"
"R (1,0.1;1.1,0.1) V1(1.65,0.05;1.65,0.05) 6"
)
}
TEST(extraction_meander)
{
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 1000),
db::Point (1600, 1000),
db::Point (1600, 600),
db::Point (2000, 600),
db::Point (2000, 1000),
db::Point (3600, 1000),
db::Point (3600, 600),
db::Point (4000, 600),
db::Point (4000, 1000),
db::Point (4600, 1000),
db::Point (4600, 0),
db::Point (3000, 0),
db::Point (3000, 400),
db::Point (2600, 400),
db::Point (2600, 0),
db::Point (1000, 0),
db::Point (1000, 400),
db::Point (600, 400),
db::Point (600, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::SquareCountingRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
vertex_ports.push_back (db::Point (300, 0)); // V0
vertex_ports.push_back (db::Point (4300, 1000)); // V1
std::vector<db::Polygon> polygon_ports;
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (network2s (rn),
"R V0(0.3,0;0.3,0) V1(4.3,1;4.3,1) 10.0543767445" // that is pretty much the length of the center line / width :)
)
}

View File

@ -0,0 +1,363 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "pexTriangulationRExtractor.h"
#include "tlUnitTest.h"
namespace
{
class TestableTriangulationRExtractor
: public pex::TriangulationRExtractor
{
public:
TestableTriangulationRExtractor ()
: pex::TriangulationRExtractor (0.001)
{ }
};
}
TEST(extraction)
{
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 100),
db::Point (1000, 100),
db::Point (1000, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
vertex_ports.push_back (db::Point (0, 50)); // V0
vertex_ports.push_back (db::Point (1000, 50)); // V1
std::vector<db::Polygon> polygon_ports;
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R V0 V1 10.0938"
)
}
TEST(extraction_with_polygon_ports)
{
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 100),
db::Point (1000, 100),
db::Point (1000, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100)));
polygon_ports.push_back (db::Polygon (db::Box (1000, 0, 1100, 100)));
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 P1 10"
)
}
TEST(extraction_with_polygon_ports_inside)
{
db::Point contour[] = {
db::Point (-100, 0),
db::Point (-100, 100),
db::Point (1100, 100),
db::Point (1100, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100)));
polygon_ports.push_back (db::Polygon (db::Box (1000, 0, 1100, 100)));
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 P1 10"
)
}
TEST(extraction_split_by_ports)
{
db::Point contour[] = {
db::Point (-100, 0),
db::Point (-100, 100),
db::Point (1100, 100),
db::Point (1100, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100)));
polygon_ports.push_back (db::Polygon (db::Box (1100, 0, 1200, 100)));
polygon_ports.push_back (db::Polygon (db::Box (500, 0, 600, 100)));
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 P2 5\n"
"R P1 P2 5"
)
}
TEST(extraction_split_by_butting_port)
{
db::Point contour[] = {
db::Point (-100, 0),
db::Point (-100, 100),
db::Point (1100, 100),
db::Point (1100, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100)));
polygon_ports.push_back (db::Polygon (db::Box (1100, 0, 1200, 100)));
polygon_ports.push_back (db::Polygon (db::Box (500, 100, 600, 200)));
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 P2 4.84211\n"
"R P1 P2 4.84211\n"
"R P0 P1 281.111"
)
}
TEST(extraction_with_outside_polygon_port)
{
db::Point contour[] = {
db::Point (-100, 0),
db::Point (-100, 100),
db::Point (1100, 100),
db::Point (1100, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100)));
polygon_ports.push_back (db::Polygon (db::Box (1100, 0, 1200, 100)));
polygon_ports.push_back (db::Polygon (db::Box (500, 200, 600, 300)));
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 P1 11"
)
}
TEST(extraction_with_polygon_ports_and_vertex_port_inside)
{
db::Point contour[] = {
db::Point (-100, 0),
db::Point (-100, 100),
db::Point (1100, 100),
db::Point (1100, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
vertex_ports.push_back (db::Point (-50, 50));
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100)));
polygon_ports.push_back (db::Polygon (db::Box (1000, 0, 1100, 100)));
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 V0 0\n" // shorted because V0 is inside P0
"R P0 P1 10"
)
}
static db::Polygon ellipse (const db::Box &box, int npoints)
{
npoints = std::max (3, std::min (10000000, npoints));
std::vector<db::Point> pts;
pts.reserve (npoints);
double da = M_PI * 2.0 / npoints;
for (int i = 0; i < npoints; ++i) {
double x = box.center ().x () - box.width () * 0.5 * cos (da * i);
double y = box.center ().y () + box.height () * 0.5 * sin (da * i);
pts.push_back (db::Point (x, y));
}
db::Polygon c;
c.assign_hull (pts.begin (), pts.end (), false);
return c;
}
TEST(extraction_analytic_disc)
{
db::Coord r1 = 2000;
db::Coord r2 = 10000;
db::Coord r2pin = 10000 + 1000;
db::Polygon outer = ellipse (db::Box (-r2pin, -r2pin, r2pin, r2pin), 64);
db::Polygon disc = ellipse (db::Box (-r2, -r2, r2, r2), 64);
db::Polygon inner = ellipse (db::Box (-r1, -r1, r1, r1), 64);
db::Polygon outer_port = *(db::Region (outer) - db::Region (disc)).nth (0);
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
std::vector<db::Point> vertex_ports;
std::vector<db::Polygon> polygon_ports;
polygon_ports.push_back (inner);
polygon_ports.push_back (outer_port);
rex.extract (disc, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 P1 0.245558" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000
)
rex.triangulation_parameters ().max_area = 100000 * dbu * dbu;
rex.extract (disc, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R P0 P1 0.255609" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000
)
}
TEST(extraction_meander)
{
db::Point contour[] = {
db::Point (0, 0),
db::Point (0, 1000),
db::Point (1600, 1000),
db::Point (1600, 600),
db::Point (2000, 600),
db::Point (2000, 1000),
db::Point (3600, 1000),
db::Point (3600, 600),
db::Point (4000, 600),
db::Point (4000, 1000),
db::Point (4600, 1000),
db::Point (4600, 0),
db::Point (3000, 0),
db::Point (3000, 400),
db::Point (2600, 400),
db::Point (2600, 0),
db::Point (1000, 0),
db::Point (1000, 400),
db::Point (600, 400),
db::Point (600, 0)
};
db::Polygon poly;
poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0]));
double dbu = 0.001;
pex::RNetwork rn;
pex::TriangulationRExtractor rex (dbu);
rex.triangulation_parameters ().max_area = 10000 * dbu * dbu;
rex.triangulation_parameters ().min_b = 0.3;
std::vector<db::Point> vertex_ports;
vertex_ports.push_back (db::Point (300, 0)); // V0
vertex_ports.push_back (db::Point (4300, 1000)); // V1
std::vector<db::Polygon> polygon_ports;
rex.extract (poly, vertex_ports, polygon_ports, rn);
EXPECT_EQ (rn.to_string (),
"R V0 V1 8.61417" // what is the "real" value?
)
}

View File

@ -0,0 +1,19 @@
DESTDIR_UT = $$OUT_PWD/../..
DESTDIR = $$OUT_PWD/..
TARGET = pex_tests
include($$PWD/../../lib_ut.pri)
SOURCES = \
pexRExtractorTests.cc \
pexRNetExtractorTests.cc \
pexSquareCountingRExtractorTests.cc \
pexTriangulationRExtractorTests.cc
INCLUDEPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC
DEPENDPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC
LIBS += -L$$DESTDIR_UT -lklayout_db -lklayout_tl -lklayout_gsi -lklayout_pex

View File

@ -502,7 +502,7 @@ PyObject *c2python_func<const tl::Variant &>::operator() (const tl::Variant &c)
} else if (c.is_bool ()) {
return c2python (c.to_bool ());
} else if (c.is_a_string ()) {
return c2python (c.to_string ());
return c2python (c.to_stdstring ());
} else if (c.is_a_bytearray ()) {
return c2python (c.to_bytearray ());
} else if (c.is_long ()) {

View File

@ -13813,7 +13813,7 @@ class DPolygon:
The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.
Picking a value of 0.0 for max area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
This method has been introduced in version 0.30.
"""
@ -13912,6 +13912,24 @@ class DPolygon:
This method has been introduced in version 0.25.
"""
...
def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ...) -> List[DPolygon]:
r"""
@brief Performs a Hertel-Mehlhorn convex decomposition.
@return An array holding the polygons of the decomposition.
The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons.
The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'.
If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used.
If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines.
'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay).
This method has been introduced in version 0.30.1.
"""
...
def holes(self) -> int:
r"""
@brief Returns the number of holes
@ -14926,7 +14944,7 @@ class DSimplePolygon:
The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.
Picking a value of 0.0 for max area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
This method has been introduced in version 0.30.
"""
@ -15003,6 +15021,24 @@ class DSimplePolygon:
This method has been introduced in version 0.25.
"""
...
def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ...) -> List[DSimplePolygon]:
r"""
@brief Performs a Hertel-Mehlhorn convex decomposition.
@return An array holding the polygons of the decomposition.
The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons.
The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'.
If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used.
If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines.
'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay).
This method has been introduced in version 0.30.1.
"""
...
def inside(self, p: DPoint) -> bool:
r"""
@brief Gets a value indicating whether the given point is inside the polygon
@ -34922,11 +34958,11 @@ class Instance:
Starting with version 0.25 the displacement is of vector type.
Setter:
@brief Sets the displacement vector for the 'b' axis in micrometer units
@brief Sets the displacement vector for the 'b' axis
Like \b= with an integer displacement, this method will set the displacement vector but it accepts a vector in micrometer units that is of \DVector type. The vector will be translated to database units internally.
If the instance was not an array instance before it is made one.
This method has been introduced in version 0.25.
This method has been introduced in version 0.23. Starting with version 0.25 the displacement is of vector type.
"""
cell: Cell
r"""
@ -34969,10 +35005,9 @@ class Instance:
@brief Gets the complex transformation of the instance or the first instance in the array
This method is always valid compared to \trans, since simple transformations can be expressed as complex transformations as well.
Setter:
@brief Sets the complex transformation of the instance or the first instance in the array (in micrometer units)
This method sets the transformation the same way as \cplx_trans=, but the displacement of this transformation is given in micrometer units. It is internally translated into database units.
@brief Sets the complex transformation of the instance or the first instance in the array
This method has been introduced in version 0.25.
This method has been introduced in version 0.23.
"""
da: DVector
r"""
@ -46702,17 +46737,17 @@ class NetTerminalRef:
@overload
def device(self) -> Device:
r"""
@brief Gets the device reference (non-const version).
@brief Gets the device reference.
Gets the device object that this connection is made to.
This constness variant has been introduced in version 0.26.8
"""
...
@overload
def device(self) -> Device:
r"""
@brief Gets the device reference.
@brief Gets the device reference (non-const version).
Gets the device object that this connection is made to.
This constness variant has been introduced in version 0.26.8
"""
...
def device_class(self) -> DeviceClass:
@ -47724,17 +47759,17 @@ class Netlist:
@overload
def circuit_by_name(self, name: str) -> Circuit:
r"""
@brief Gets the circuit object for a given name.
@brief Gets the circuit object for a given name (const version).
If the name is not a valid circuit name, nil is returned.
This constness variant has been introduced in version 0.26.8.
"""
...
@overload
def circuit_by_name(self, name: str) -> Circuit:
r"""
@brief Gets the circuit object for a given name (const version).
@brief Gets the circuit object for a given name.
If the name is not a valid circuit name, nil is returned.
This constness variant has been introduced in version 0.26.8.
"""
...
@overload
@ -47752,7 +47787,6 @@ class Netlist:
@brief Gets the circuit objects for a given name filter (const version).
The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern.
This constness variant has been introduced in version 0.26.8.
"""
...
@ -53583,11 +53617,13 @@ class Polygon:
The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.
The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
The area value is given in terms of DBU units.
The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.
This method has been introduced in version 0.30.
This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations.
"""
...
@overload
@ -53597,7 +53633,7 @@ class Polygon:
This variant of the triangulation function accepts an array of additional vertexes for the triangulation.
This method has been introduced in version 0.30.
This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations.
"""
...
def destroy(self) -> None:
@ -53684,6 +53720,28 @@ class Polygon:
This method has been introduced in version 0.25.
"""
...
def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ..., dbu: Optional[float] = ...) -> Region:
r"""
@brief Performs a Hertel-Mehlhorn convex decomposition.
@return A \Region holding the polygons of the decomposition.
The resulting region is in 'no merged semantics' mode, to avoid re-merging of the polygons during following operations.
The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons.
The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'.
If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used.
If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines.
'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay).
The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.
This method has been introduced in version 0.30.1.
"""
...
def holes(self) -> int:
r"""
@brief Returns the number of holes
@ -62999,11 +63057,12 @@ class Shape:
This method has been introduced in version 0.23.
Setter:
@brief Sets the lower left point of the box
@brief Sets the lower left corner of the box with the point being given in micrometer units
Applies to boxes only. Changes the lower left point of the box and throws an exception if the shape is not a box.
Translation from micrometer units to database units is done internally.
This method has been introduced in version 0.23.
This method has been introduced in version 0.25.
"""
box_p2: Point
r"""
@ -66722,11 +66781,13 @@ class SimplePolygon:
The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.
The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation.
The area value is given in terms of DBU units.
The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.
This method has been introduced in version 0.30.
This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations.
"""
...
@overload
@ -66736,7 +66797,7 @@ class SimplePolygon:
This variant of the triangulation function accepts an array of additional vertexes for the triangulation.
This method has been introduced in version 0.30.
This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations.
"""
...
def destroy(self) -> None:
@ -66801,6 +66862,27 @@ class SimplePolygon:
This method has been introduced in version 0.25.
"""
...
def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ..., dbu: Optional[float] = ...) -> Region:
r"""
@brief Performs a Hertel-Mehlhorn convex decomposition.
@return A \Region holding the polygons of the decomposition.
The resulting region is in 'no merged semantics' mode, to avoid re-merging of the polygons during following operations.
The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons.
The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'.
If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used.
If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines.
'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay).
The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.
This method has been introduced in version 0.30.1.
"""
...
def inside(self, p: Point) -> bool:
r"""
@brief Gets a value indicating whether the given point is inside the polygon
@ -67556,17 +67638,23 @@ class SubCircuit(NetlistObject):
@overload
def circuit(self) -> Circuit:
r"""
@brief Gets the circuit the subcircuit lives in.
@brief Gets the circuit the subcircuit lives in (non-const version).
This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref.
This constness variant has been introduced in version 0.26.8
"""
...
@overload
def circuit(self) -> Circuit:
r"""
@brief Gets the circuit the subcircuit lives in (non-const version).
@brief Gets the circuit the subcircuit lives in.
This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref.
This constness variant has been introduced in version 0.26.8
"""
...
@overload
def circuit_ref(self) -> Circuit:
r"""
@brief Gets the circuit referenced by the subcircuit.
"""
...
@overload
@ -67579,12 +67667,6 @@ class SubCircuit(NetlistObject):
"""
...
@overload
def circuit_ref(self) -> Circuit:
r"""
@brief Gets the circuit referenced by the subcircuit.
"""
...
@overload
def connect_pin(self, pin: Pin, net: Net) -> None:
r"""
@brief Connects the given pin to the specified net.
@ -68287,7 +68369,8 @@ class Text:
Setter:
@brief Sets the vertical alignment
This is the version accepting integer values. It's provided for backward compatibility.
This property specifies how the text is aligned relative to the anchor point.
This property has been introduced in version 0.22 and extended to enums in 0.28.
"""
x: int
r"""

View File

@ -0,0 +1,4 @@
import sys
from ..pexcore import __all__
from ..pexcore import *

View File

@ -0,0 +1,5 @@
from typing import Any, ClassVar, Dict, Sequence, List, Iterator, Optional
from typing import overload
from __future__ import annotations
import klayout.tl as tl
import klayout.db as db

14
src/pymod/pex/pex.pro Normal file
View File

@ -0,0 +1,14 @@
TARGET = pexcore
REALMODULE = pex
PYI = pexcore.pyi
include($$PWD/../pymod.pri)
SOURCES = \
pexMain.cc \
HEADERS += \
LIBS += -lklayout_pex

31
src/pymod/pex/pexMain.cc Normal file
View File

@ -0,0 +1,31 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../pymodHelper.h"
#include "pexMain.h"
static PyObject *pex_module_init (const char *pymod_name, const char *mod_name, const char *mod_description)
{
return module_init (pymod_name, mod_name, mod_description);
}
DEFINE_PYMOD_WITH_INIT(pexcore, "pex", "KLayout core module 'pex'", pex_module_init)

24
src/pymod/pex/pexMain.h Normal file
View File

@ -0,0 +1,24 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2025 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
// to force linking of the pex module
#include "../../pex/pex/pexForceLink.h"

View File

@ -4,6 +4,7 @@ include($$PWD/../klayout.pri)
TEMPLATE = subdirs
SUBDIRS = \
db \
pex \
tl \
rdb \
lib \

View File

@ -84,6 +84,7 @@ PYMODTEST (bridge, "bridge.py")
PYMODTEST (import_tl, "import_tl.py")
PYMODTEST (import_db, "import_db.py")
PYMODTEST (import_pex, "import_pex.py")
PYMODTEST (klayout_db_tests, "klayout_db_tests.py")
PYMODTEST (import_rdb, "import_rdb.py")
PYMODTEST (import_lay, "import_lay.py")

View File

@ -29,6 +29,8 @@
#include "gsiDecl.h"
#include <ruby/encoding.h>
namespace rba
{
@ -120,9 +122,17 @@ tl::Variant ruby2c<tl::Variant> (VALUE rval)
}
} else if (TYPE (rval) == T_STRING) {
return tl::Variant (ruby2c<const char *> (rval));
// UTF-8 encoded strings are taken to be string, others are byte strings
// At least this ensures consistency for a full Ruby-C++ turnaround cycle.
if (rb_enc_from_index (rb_enc_get_index (rval)) == rb_utf8_encoding ()) {
return tl::Variant (ruby2c<std::string> (rval));
} else {
return tl::Variant (ruby2c<std::vector<char> > (rval));
}
} else {
return tl::Variant (ruby2c<const char *> (rba_safe_obj_as_string (rval)));
return tl::Variant (ruby2c<std::string> (rba_safe_obj_as_string (rval)));
}
}
@ -261,7 +271,7 @@ VALUE c2ruby<tl::Variant> (const tl::Variant &c)
} else if (c.is_bool ()) {
return c2ruby<bool> (c.to_bool ());
} else if (c.is_a_string ()) {
return c2ruby<std::string> (c.to_string ());
return c2ruby<std::string> (c.to_stdstring ());
} else if (c.is_a_bytearray ()) {
return c2ruby<std::vector<char> > (c.to_bytearray ());
} else if (c.is_long () || c.is_char ()) {

View File

@ -461,7 +461,7 @@ inline VALUE c2ruby<float> (const float &c)
template <>
inline VALUE c2ruby<std::string> (const std::string &c)
{
return rb_str_new (c.c_str (), long (c.size ()));
return rb_utf8_str_new (c.c_str (), long (c.size ()));
}
template <>
@ -488,7 +488,7 @@ inline VALUE c2ruby<QString> (const QString &qs)
return Qnil;
} else {
std::string c (tl::to_string (qs));
return rb_str_new (c.c_str (), long (c.size ()));
return rb_utf8_str_new (c.c_str (), long (c.size ()));
}
}
#endif
@ -507,9 +507,9 @@ inline VALUE c2ruby<const char *> (const char * const & s)
{
if (! s) {
static const char null_string[] = "(null)";
return rb_str_new (null_string, sizeof (null_string) - 1);
return rb_utf8_str_new (null_string, sizeof (null_string) - 1);
} else {
return rb_str_new (s, long (strlen (s)));
return rb_utf8_str_new (s, long (strlen (s)));
}
}

View File

@ -679,7 +679,7 @@ struct reader<gsi::StringType>
if (!a.get ()) {
*ret = Qnil;
} else {
*ret = rb_str_new (a->c_str (), long (a->size ()));
*ret = rb_utf8_str_new (a->c_str (), long (a->size ()));
}
}
};

View File

@ -135,6 +135,20 @@ inline void rb_hash_clear(VALUE hash)
#endif
#if HAVE_RUBY_VERSION_CODE < 20200
#include <ruby/encoding.h>
// Ruby <2.2 does not have this useful function
inline VALUE rb_utf8_str_new (const char *ptr, long len)
{
VALUE str = rb_str_new (ptr, len);
rb_enc_associate_index (str, rb_utf8_encindex ());
return str;
}
#endif
typedef VALUE (*ruby_func)(ANYARGS);
/**

View File

@ -142,6 +142,7 @@ RUBYTEST (dbTransTest, "dbTransTest.rb")
RUBYTEST (dbVectorTest, "dbVectorTest.rb")
RUBYTEST (dbUtilsTests, "dbUtilsTests.rb")
RUBYTEST (dbTechnologies, "dbTechnologies.rb")
RUBYTEST (pexTests, "pexTests.rb")
RUBYTEST (edtTest, "edtTest.rb")
RUBYTEST (extNetTracer, "extNetTracer.rb")
RUBYTEST (imgObject, "imgObject.rb")

View File

@ -45,12 +45,13 @@ QTextCodec *ms_system_codec = 0;
QString to_qstring (const std::string &s)
{
return QString::fromUtf8 (s.c_str ());
return QString::fromUtf8 (s.c_str (), s.size ());
}
std::string to_string (const QString &s)
{
return std::string (s.toUtf8 ().constData ());
auto utf8 = s.toUtf8 ();
return std::string (utf8.constData (), utf8.size ());
}
#if !defined(_WIN32)
@ -70,7 +71,7 @@ std::string string_to_system (const std::string &s)
initialize_codecs ();
}
QString qs = QString::fromUtf8 (s.c_str ());
QString qs = QString::fromUtf8 (s.c_str (), s.size ());
return std::string (ms_system_codec->fromUnicode (qs).constData ());
}
#endif

View File

@ -71,6 +71,7 @@
// and the plugins/auxiliary modules (some in non-Qt case)
#include "libForceLink.h"
#include "rdbForceLink.h"
#include "pexForceLink.h"
#include "antForceLink.h"
#include "imgForceLink.h"
#include "edtForceLink.h"

View File

@ -1,8 +1,8 @@
INCLUDEPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC
DEPENDPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC
INCLUDEPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$PEX_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC
DEPENDPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$PEX_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC
LIBS += "$$PYTHONLIBFILE" "$$RUBYLIBFILE" -L$$DESTDIR -lklayout_tl -lklayout_gsi -lklayout_db -lklayout_rdb -lklayout_lym -lklayout_laybasic -lklayout_layview -lklayout_ant -lklayout_img -lklayout_edt -lklayout_lib
LIBS += "$$PYTHONLIBFILE" "$$RUBYLIBFILE" -L$$DESTDIR -lklayout_tl -lklayout_gsi -lklayout_db -lklayout_pex -lklayout_rdb -lklayout_lym -lklayout_laybasic -lklayout_layview -lklayout_ant -lklayout_img -lklayout_edt -lklayout_lib
!equals(HAVE_QT, "0") {

BIN
testdata/algo/hm_decomposition_au1.gds vendored Normal file

Binary file not shown.

BIN
testdata/algo/hm_decomposition_au2.gds vendored Normal file

Binary file not shown.

BIN
testdata/algo/hm_decomposition_au3.gds vendored Normal file

Binary file not shown.

BIN
testdata/algo/hm_decomposition_au4.gds vendored Normal file

Binary file not shown.

BIN
testdata/algo/hm_decomposition_au5.gds vendored Normal file

Binary file not shown.

BIN
testdata/pex/netex_test1.gds vendored Normal file

Binary file not shown.

BIN
testdata/pex/netex_viagen1.gds vendored Normal file

Binary file not shown.

BIN
testdata/pex/netex_viagen2.gds vendored Normal file

Binary file not shown.

37
testdata/pymod/import_pex.py vendored Executable file
View File

@ -0,0 +1,37 @@
# KLayout Layout Viewer
# Copyright (C) 2006-2025 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import testprep
import klayout.pex as pex
import unittest
import sys
import os
# Tests the basic abilities of the module
class BasicTest(unittest.TestCase):
def test_1(self):
self.assertEqual("RExtractor" in pex.__all__, True)
# run unit tests
if __name__ == '__main__':
suite = unittest.TestSuite()
suite = unittest.TestLoader().loadTestsFromTestCase(BasicTest)
if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful():
sys.exit(1)

View File

@ -2994,8 +2994,12 @@ class BasicTest(unittest.TestCase):
qba = pya.A.ia_cref_to_qba([ 16, 42, 0, 8 ])
if sys.version_info < (3, 0):
self.assertEqual(repr(qba), "bytearray(b'\\x10*\\x00\\x08')")
self.assertEqual(repr(pya.A.ft_qba(qba)), "bytearray(b'\\x10*\\x00\\x08')")
self.assertEqual(repr(pya.A.ft_var(qba)), "bytearray(b'\\x10*\\x00\\x08')")
else:
self.assertEqual(repr(qba), "b'\\x10*\\x00\\x08'")
self.assertEqual(repr(pya.A.ft_qba(qba)), "b'\\x10*\\x00\\x08'")
self.assertEqual(repr(pya.A.ft_var(qba)), "b'\\x10*\\x00\\x08'")
self.assertEqual(pya.A.qba_to_ia(qba), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.qba_cref_to_ia(qba), [ 16, 42, 0, 8 ])
@ -3079,26 +3083,29 @@ class BasicTest(unittest.TestCase):
if "ia_cref_to_qs" in pya.A.__dict__:
qs = pya.A.ia_cref_to_qs([ 16, 42, 0, 8 ])
self.assertEqual(repr(qs), "'\\x10*\\x00\\x08'")
qs = pya.A.ia_cref_to_qs([ 16, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(qs), "'\\x10*\\x00\\x08\u03a9'")
# full cycle must preserve encoding, also for var
self.assertEqual(repr(pya.A.ft_qs(qs)), "'\\x10*\\x00\\x08\u03a9'")
self.assertEqual(repr(pya.A.ft_var(qs)), "'\\x10*\\x00\\x08\u03a9'")
self.assertEqual(pya.A.qs_to_ia(qs), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.qs_cref_to_ia(qs), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.qs_cptr_to_ia(qs), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.qs_ref_to_ia(qs), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.qs_ptr_to_ia(qs), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.qs_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
self.assertEqual(pya.A.qs_cref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
self.assertEqual(pya.A.qs_cptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
self.assertEqual(pya.A.qs_ref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
self.assertEqual(pya.A.qs_ptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
qs = pya.A.ia_cref_to_qs_cref([ 17, 42, 0, 8 ])
self.assertEqual(repr(qs), "'\\x11*\\x00\\x08'")
qs = pya.A.ia_cref_to_qs_cref([ 17, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(qs), "'\\x11*\\x00\\x08\u03a9'")
qs = pya.A.ia_cref_to_qs_ref([ 18, 42, 0, 8 ])
self.assertEqual(repr(qs), "'\\x12*\\x00\\x08'")
qs = pya.A.ia_cref_to_qs_ref([ 18, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(qs), "'\\x12*\\x00\\x08\u03a9'")
qs = pya.A.ia_cref_to_qs_cptr([ 19, 42, 0, 8 ])
self.assertEqual(repr(qs), "'\\x13*\\x00\\x08'")
qs = pya.A.ia_cref_to_qs_cptr([ 19, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(qs), "'\\x13*\\x00\\x08\u03a9'")
qs = pya.A.ia_cref_to_qs_ptr([ 20, 42, 0, 8 ])
self.assertEqual(repr(qs), "'\\x14*\\x00\\x08'")
qs = pya.A.ia_cref_to_qs_ptr([ 20, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(qs), "'\\x14*\\x00\\x08\u03a9'")
self.assertEqual(pya.A.qs_to_ia('\x00\x01\x02'), [ 0, 1, 2 ])
@ -3137,29 +3144,42 @@ class BasicTest(unittest.TestCase):
if "ia_cref_to_ql1s" in pya.A.__dict__:
ql1s = pya.A.ia_cref_to_ql1s([ 16, 42, 0, 8 ])
self.assertEqual(repr(ql1s), "'\\x10*\\x00\\x08'")
ql1s = pya.A.ia_cref_to_ql1s([ 16, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(ql1s), "'\\x10*\\x00\\x08\u00a9'")
self.assertEqual(pya.A.ql1s_to_ia(ql1s), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8 ])
self.assertEqual(pya.A.ql1s_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
self.assertEqual(pya.A.ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
self.assertEqual(pya.A.ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
self.assertEqual(pya.A.ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
self.assertEqual(pya.A.ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
ql1s = pya.A.ia_cref_to_ql1s_cref([ 17, 42, 0, 8 ])
self.assertEqual(repr(ql1s), "'\\x11*\\x00\\x08'")
ql1s = pya.A.ia_cref_to_ql1s_cref([ 17, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(ql1s), "'\\x11*\\x00\\x08\u00a9'")
ql1s = pya.A.ia_cref_to_ql1s_ref([ 18, 42, 0, 8 ])
self.assertEqual(repr(ql1s), "'\\x12*\\x00\\x08'")
ql1s = pya.A.ia_cref_to_ql1s_ref([ 18, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(ql1s), "'\\x12*\\x00\\x08\u00a9'")
ql1s = pya.A.ia_cref_to_ql1s_cptr([ 19, 42, 0, 8 ])
self.assertEqual(repr(ql1s), "'\\x13*\\x00\\x08'")
ql1s = pya.A.ia_cref_to_ql1s_cptr([ 19, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(ql1s), "'\\x13*\\x00\\x08\u00a9'")
ql1s = pya.A.ia_cref_to_ql1s_ptr([ 20, 42, 0, 8 ])
self.assertEqual(repr(ql1s), "'\\x14*\\x00\\x08'")
ql1s = pya.A.ia_cref_to_ql1s_ptr([ 20, 42, 0, 8, 0x03a9 ])
self.assertEqual(repr(ql1s), "'\\x14*\\x00\\x08\u00a9'")
self.assertEqual(pya.A.ql1s_to_ia('\x00\x01\x02'), [ 0, 1, 2 ])
def test_utf8Strings(self):
# UTF8 strings (non-Qt)
s = "\u0010*\u0000\b\u03a9"
self.assertEqual(repr(s), "'\\x10*\\x00\\x08\u03a9'")
# full cycle must preserve encoding, also for var
self.assertEqual(repr(pya.A.ft_str(s)), "'\\x10*\\x00\\x08\u03a9'")
self.assertEqual(repr(pya.A.ft_var(s)), "'\\x10*\\x00\\x08\u03a9'")
# NUL character terminates in const char * mode:
self.assertEqual(repr(pya.A.ft_cptr(s)), "'\\x10*'")
def test_binaryStrings(self):
# binary strings (non-Qt)
@ -3167,8 +3187,13 @@ class BasicTest(unittest.TestCase):
ba = pya.A.ia_cref_to_ba([ 17, 42, 0, 8 ])
if sys.version_info < (3, 0):
self.assertEqual(repr(ba), "bytearray(b'\\x11*\\x00\\x08')")
self.assertEqual(repr(pya.A.ft_cv(ba)), "bytearray(b'\\x11*\\x00\\x08')")
self.assertEqual(repr(pya.A.ft_var(ba)), "bytearray(b'\\x11*\\x00\\x08')")
else:
self.assertEqual(repr(ba), "b'\\x11*\\x00\\x08'")
self.assertEqual(repr(pya.A.ft_cv(ba)), "b'\\x11*\\x00\\x08'")
self.assertEqual(repr(pya.A.ft_var(ba)), "b'\\x11*\\x00\\x08'")
self.assertEqual(pya.A.ba_to_ia(ba), [ 17, 42, 0, 8 ])
self.assertEqual(pya.A.ba_cref_to_ia(ba), [ 17, 42, 0, 8 ])
self.assertEqual(pya.A.ba_cptr_to_ia(ba), [ 17, 42, 0, 8 ])

View File

@ -2958,6 +2958,9 @@ class Basic_TestClass < TestBase
qba = RBA::A::ia_cref_to_qba([ 16, 42, 0, 8 ])
assert_equal(qba.inspect, "\"\\x10*\\x00\\b\"")
# full cycle must preserve encoding, also for var
assert_equal(RBA::A::ft_qba(qba).inspect, "\"\\x10*\\x00\\b\"")
assert_equal(RBA::A::ft_var(qba).inspect, "\"\\x10*\\x00\\b\"")
assert_equal(RBA::A::qba_to_ia(qba), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::qba_cref_to_ia(qba), [ 16, 42, 0, 8 ])
@ -3016,23 +3019,33 @@ class Basic_TestClass < TestBase
if RBA::A.respond_to?(:ia_cref_to_qs)
qs = RBA::A::ia_cref_to_qs([ 16, 42, 0, 8 ])
assert_equal(qs.inspect, "\"\\x10*\\x00\\b\"")
qs = RBA::A::ia_cref_to_qs([ 16, 42, 0, 8, 0x03a9 ])
assert_equal(qs.encoding.name, "UTF-8")
assert_equal(qs, "\u0010*\u0000\b\u03a9")
# full cycle must preserve encoding, also for var
assert_equal(RBA::A::ft_qs(qs).encoding.name, "UTF-8")
assert_equal(RBA::A::ft_qs(qs), "\u0010*\u0000\b\u03a9")
assert_equal(RBA::A::ft_var(qs).encoding.name, "UTF-8")
assert_equal(RBA::A::ft_var(qs), "\u0010*\u0000\b\u03a9")
assert_equal(RBA::A::qs_to_ia(qs), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::qs_cref_to_ia(qs), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::qs_cptr_to_ia(qs), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::qs_ref_to_ia(qs), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::qs_ptr_to_ia(qs), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::qs_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
assert_equal(RBA::A::qs_cref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
assert_equal(RBA::A::qs_cptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
assert_equal(RBA::A::qs_ref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
assert_equal(RBA::A::qs_ptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ])
qs = RBA::A::ia_cref_to_qs_cref([ 17, 42, 0, 8 ])
assert_equal(qs.inspect, "\"\\x11*\\x00\\b\"")
qs = RBA::A::ia_cref_to_qs_ref([ 18, 42, 0, 8 ])
assert_equal(qs.inspect, "\"\\x12*\\x00\\b\"")
qs = RBA::A::ia_cref_to_qs_cptr([ 19, 42, 0, 8 ])
assert_equal(qs.inspect, "\"\\x13*\\x00\\b\"")
qs = RBA::A::ia_cref_to_qs_ptr([ 20, 42, 0, 8 ])
assert_equal(qs.inspect, "\"\\x14*\\x00\\b\"")
qs = RBA::A::ia_cref_to_qs_cref([ 17, 42, 0, 8, 0x03a9 ])
assert_equal(qs.encoding.name, "UTF-8")
assert_equal(qs, "\u0011*\u0000\b\u03a9")
qs = RBA::A::ia_cref_to_qs_ref([ 18, 42, 0, 8, 0x03a9 ])
assert_equal(qs.encoding.name, "UTF-8")
assert_equal(qs, "\u0012*\u0000\b\u03a9")
qs = RBA::A::ia_cref_to_qs_cptr([ 19, 42, 0, 8, 0x03a9 ])
assert_equal(qs.encoding.name, "UTF-8")
assert_equal(qs, "\u0013*\u0000\b\u03a9")
qs = RBA::A::ia_cref_to_qs_ptr([ 20, 42, 0, 8, 0x03a9 ])
assert_equal(qs.encoding.name, "UTF-8")
assert_equal(qs, "\u0014*\u0000\b\u03a9")
assert_equal(RBA::A::qs_to_ia("\x00\x01\x02"), [ 0, 1, 2 ])
@ -3046,23 +3059,28 @@ class Basic_TestClass < TestBase
if RBA::A.respond_to?(:ia_cref_to_ql1s)
ql1s = RBA::A::ia_cref_to_ql1s([ 16, 42, 0, 8 ])
assert_equal(ql1s.inspect, "\"\\x10*\\x00\\b\"")
ql1s = RBA::A::ia_cref_to_ql1s([ 16, 42, 0, 8, 0x03a9 ])
assert_equal(ql1s.encoding.name, "UTF-8")
assert_equal(ql1s, "\u0010*\u0000\b\u00a9")
assert_equal(RBA::A::ql1s_to_ia(ql1s), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ql1s_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
assert_equal(RBA::A::ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
assert_equal(RBA::A::ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
assert_equal(RBA::A::ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
assert_equal(RBA::A::ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ])
ql1s = RBA::A::ia_cref_to_ql1s_cref([ 17, 42, 0, 8 ])
assert_equal(ql1s.inspect, "\"\\x11*\\x00\\b\"")
ql1s = RBA::A::ia_cref_to_ql1s_ref([ 18, 42, 0, 8 ])
assert_equal(ql1s.inspect, "\"\\x12*\\x00\\b\"")
ql1s = RBA::A::ia_cref_to_ql1s_cptr([ 19, 42, 0, 8 ])
assert_equal(ql1s.inspect, "\"\\x13*\\x00\\b\"")
ql1s = RBA::A::ia_cref_to_ql1s_ptr([ 20, 42, 0, 8 ])
assert_equal(ql1s.inspect, "\"\\x14*\\x00\\b\"")
ql1s = RBA::A::ia_cref_to_ql1s_cref([ 17, 42, 0, 8, 0xa9 ])
assert_equal(ql1s.encoding.name, "UTF-8")
assert_equal(ql1s, "\u0011*\u0000\b\u00a9")
ql1s = RBA::A::ia_cref_to_ql1s_ref([ 18, 42, 0, 8, 0xa9 ])
assert_equal(ql1s.encoding.name, "UTF-8")
assert_equal(ql1s, "\u0012*\u0000\b\u00a9")
ql1s = RBA::A::ia_cref_to_ql1s_cptr([ 19, 42, 0, 8, 0xa9 ])
assert_equal(ql1s.encoding.name, "UTF-8")
assert_equal(ql1s, "\u0013*\u0000\b\u00a9")
ql1s = RBA::A::ia_cref_to_ql1s_ptr([ 20, 42, 0, 8, 0xa9 ])
assert_equal(ql1s.encoding.name, "UTF-8")
assert_equal(ql1s, "\u0014*\u0000\b\u00a9")
assert_equal(RBA::A::ql1s_to_ia("\x00\x01\x02"), [ 0, 1, 2 ])
@ -3077,7 +3095,7 @@ class Basic_TestClass < TestBase
if RBA::A.respond_to?(:ia_cref_to_qsv)
qsv = RBA::A::ia_cref_to_qsv([ 16, 42, 0, 8 ])
assert_equal(qsv.inspect, "\"\\x10*\\x00\\b\"")
assert_equal(qsv.inspect, "\"\\u0010*\\u0000\\b\"")
assert_equal(RBA::A::qsv_to_ia(qsv), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::qsv_cref_to_ia(qsv), [ 16, 42, 0, 8 ])
@ -3086,13 +3104,13 @@ class Basic_TestClass < TestBase
assert_equal(RBA::A::qsv_ptr_to_ia(qsv), [ 16, 42, 0, 8 ])
qsv = RBA::A::ia_cref_to_qsv_cref([ 17, 42, 0, 8 ])
assert_equal(qsv.inspect, "\"\\x11*\\x00\\b\"")
assert_equal(qsv.inspect, "\"\\u0011*\\u0000\\b\"")
qsv = RBA::A::ia_cref_to_qsv_ref([ 18, 42, 0, 8 ])
assert_equal(qsv.inspect, "\"\\x12*\\x00\\b\"")
assert_equal(qsv.inspect, "\"\\u0012*\\u0000\\b\"")
qsv = RBA::A::ia_cref_to_qsv_cptr([ 19, 42, 0, 8 ])
assert_equal(qsv.inspect, "\"\\x13*\\x00\\b\"")
assert_equal(qsv.inspect, "\"\\u0013*\\u0000\\b\"")
qsv = RBA::A::ia_cref_to_qsv_ptr([ 20, 42, 0, 8 ])
assert_equal(qsv.inspect, "\"\\x14*\\x00\\b\"")
assert_equal(qsv.inspect, "\"\\u0014*\\u0000\\b\"")
assert_equal(RBA::A::qsv_to_ia("\x00\x01\x02"), [ 0, 1, 2 ])
@ -3100,18 +3118,39 @@ class Basic_TestClass < TestBase
end
def test_utf8Strings
# UTF8 strings (non-Qt)
s = "\u0010*\u0000\b\u03a9"
assert_equal(s.encoding.name, "UTF-8")
# full cycle must preserve encoding, also for var
assert_equal(RBA::A::ft_str(s).encoding.name, "UTF-8")
assert_equal(RBA::A::ft_str(s), "\u0010*\u0000\b\u03a9")
assert_equal(RBA::A::ft_var(s).encoding.name, "UTF-8")
assert_equal(RBA::A::ft_var(s), "\u0010*\u0000\b\u03a9")
# NUL character terminates in const char * mode:
assert_equal(RBA::A::ft_cptr(s).encoding.name, "UTF-8")
assert_equal(RBA::A::ft_cptr(s), "\u0010*")
end
def test_binaryStrings
# binary strings (non-Qt)
ba = RBA::A::ia_cref_to_ba([ 16, 42, 1, 8 ])
assert_equal(ba.inspect, "\"\\x10*\\x01\\b\"")
ba = RBA::A::ia_cref_to_ba([ 16, 42, 0, 8 ])
assert_equal(ba.inspect, "\"\\x10*\\x00\\b\"")
# full cycle must preserve encoding, also for var
assert_equal(RBA::A::ft_cv(ba).inspect, "\"\\x10*\\x00\\b\"")
assert_equal(RBA::A::ft_var(ba).inspect, "\"\\x10*\\x00\\b\"")
assert_equal(RBA::A::ba_to_ia(ba), [ 16, 42, 1, 8 ])
assert_equal(RBA::A::ba_cref_to_ia(ba), [ 16, 42, 1, 8 ])
assert_equal(RBA::A::ba_cptr_to_ia(ba), [ 16, 42, 1, 8 ])
assert_equal(RBA::A::ba_ref_to_ia(ba), [ 16, 42, 1, 8 ])
assert_equal(RBA::A::ba_ptr_to_ia(ba), [ 16, 42, 1, 8 ])
assert_equal(RBA::A::ba_to_ia(ba), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ba_cref_to_ia(ba), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ba_cptr_to_ia(ba), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ba_ref_to_ia(ba), [ 16, 42, 0, 8 ])
assert_equal(RBA::A::ba_ptr_to_ia(ba), [ 16, 42, 0, 8 ])
ba = RBA::A::ia_cref_to_ba_cref([ 17, 42, 0, 8 ])
assert_equal(ba.inspect, "\"\\x11*\\x00\\b\"")

View File

@ -994,15 +994,41 @@ class DBPolygon_TestClass < TestBase
assert_equal(p.delaunay(0.0, 0.5).to_s, "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)")
assert_equal(p.delaunay(20000, 0.0).to_s, "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)")
assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)")
assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(1000,0;50,50;1000,100);(0,0;50,50;1000,0)")
p = RBA::DPolygon::new(RBA::DBox::new(0, 0, 1000, 100))
assert_equal(p.delaunay.collect(&:to_s).join(";"), "(0,0;0,100;1000,100);(0,0;1000,100;1000,0)")
assert_equal(p.delaunay.each.collect(&:to_s).join(";"), "(0,0;0,100;1000,100);(0,0;1000,100;1000,0)")
assert_equal(p.delaunay(0.0, 0.5).collect(&:to_s).join(";"), "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)")
assert_equal(p.delaunay(20000, 0.0).collect(&:to_s).join(";"), "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)")
assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).collect(&:to_s).join(";"), "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)")
assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).collect(&:to_s).join(";"), "(0,0;0,100;50,50);(50,50;0,100;1000,100);(1000,0;50,50;1000,100);(0,0;50,50;1000,0)")
end
def sorted_polygons(arr)
arr.each.collect { |p| p.downcast.to_s }.sort.join(";")
end
def sorted_dpolygons(arr)
arr.each.collect { |p| p.to_s }.sort.join(";")
end
def test_hm_decomposition
p = RBA::Polygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ])
assert_equal(sorted_polygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_polygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_polygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_polygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_polygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)")
p = RBA::DPolygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ])
assert_equal(sorted_dpolygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_dpolygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_dpolygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_dpolygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_dpolygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)")
end

View File

@ -426,18 +426,43 @@ class DBSimplePolygon_TestClass < TestBase
assert_equal(p.delaunay(0.0, 0.5).to_s, "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)")
assert_equal(p.delaunay(20000, 0.0).to_s, "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)")
assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)")
assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(1000,0;50,50;1000,100);(0,0;50,50;1000,0)")
p = RBA::DSimplePolygon::new(RBA::DBox::new(0, 0, 1000, 100))
assert_equal(p.delaunay.collect(&:to_s).join(";"), "(0,0;0,100;1000,100);(0,0;1000,100;1000,0)")
assert_equal(p.delaunay(20000, 0.0).each.collect(&:to_s).join(";"), "(0,0;250,50;500,0) props={};(500,0;250,50;500,100) props={};(250,50;0,100;500,100) props={};(0,0;0,100;250,50) props={};(500,0;500,100;750,50) props={};(500,0;750,50;1000,0) props={};(1000,0;750,50;1000,100) props={};(750,50;500,100;1000,100) props={}")
assert_equal(p.delaunay.each.collect(&:to_s).join(";"), "(0,0;0,100;1000,100) props={};(0,0;1000,100;1000,0) props={}")
assert_equal(p.delaunay(0.0, 0.5).collect(&:to_s).join(";"), "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)")
assert_equal(p.delaunay(20000, 0.0).collect(&:to_s).join(";"), "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)")
assert_equal(p.delaunay(0.0, 0.5).each.collect(&:to_s).join(";"), "(0,0;0,100;250,0) props={};(250,0;500,100;500,0) props={};(250,0;0,100;500,100) props={};(750,0;1000,100;1000,0) props={};(500,0;500,100;750,0) props={};(750,0;500,100;1000,100) props={}")
assert_equal(p.delaunay(20000, 0.0).each.collect(&:to_s).join(";"), "(0,0;250,50;500,0) props={};(500,0;250,50;500,100) props={};(250,50;0,100;500,100) props={};(0,0;0,100;250,50) props={};(500,0;500,100;750,50) props={};(500,0;750,50;1000,0) props={};(1000,0;750,50;1000,100) props={};(750,50;500,100;1000,100) props={}")
assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).collect(&:to_s).join(";"), "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)")
assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).each.collect(&:to_s).join(";"), "(0,0;0,100;50,50) props={};(50,50;0,100;1000,100) props={};(1000,0;50,50;1000,100) props={};(0,0;50,50;1000,0) props={}")
end
def sorted_polygons(arr)
arr.each.collect { |p| p.downcast.to_s }.sort.join(";")
end
def sorted_dpolygons(arr)
arr.each.collect { |p| p.to_s }.sort.join(";")
end
def test_hm_decomposition
p = RBA::SimplePolygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ])
assert_equal(sorted_polygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_polygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_polygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_polygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_polygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)")
p = RBA::DSimplePolygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ])
assert_equal(sorted_dpolygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_dpolygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_dpolygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)")
assert_equal(sorted_dpolygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)")
assert_equal(sorted_dpolygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)")
end
end
load("test_epilogue.rb")

268
testdata/ruby/pexTests.rb vendored Normal file
View File

@ -0,0 +1,268 @@
# encoding: UTF-8
# KLayout Layout Viewer
# Copyright (C) 2006-2025 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
if !$:.member?(File::dirname($0))
$:.push(File::dirname($0))
end
load("test_prologue.rb")
class PEX_TestClass < TestBase
# PEX basics
def test_1_Basic
rn = RBA::RNetwork::new
a = rn.create_node(RBA::RNode::VertexPort, 1)
b = rn.create_node(RBA::RNode::Internal, 2)
c = rn.create_node(RBA::RNode::PolygonPort, 3)
d = rn.create_node(RBA::RNode::PolygonPort, 3, 17)
assert_equal(a.type, RBA::RNode::VertexPort)
assert_equal(a.port_index, 1)
assert_equal(a.object_id, a.object_id)
assert_not_equal(a.object_id, b.object_id)
assert_equal(a.to_s, "V1")
assert_equal(b.to_s, "$2")
assert_equal(c.to_s, "P3")
assert_equal(d.to_s, "P3.17")
rab = rn.create_element(1.0, a, b)
assert_equal(rab.a.object_id, a.object_id)
assert_equal(rab.b.object_id, b.object_id)
assert_equal(rab.to_s, "R $2 V1 1")
rn.create_element(1.0, a, b)
assert_equal(rab.to_s, "R $2 V1 0.5")
rbc = rn.create_element(1.0, b, c)
assert_equal(rn.to_s, "R $2 V1 0.5\n" + "R $2 P3 1")
assert_equal(b.each_element.collect(&:to_s).sort.join(";"), "R $2 P3 1;R $2 V1 0.5")
assert_equal(rn.each_element.collect(&:to_s).sort.join(";"), "R $2 P3 1;R $2 V1 0.5")
assert_equal(rn.each_node.collect(&:to_s).sort.join(";"), "$2;P3;P3.17;V1")
rn.simplify
assert_equal(rn.to_s, "R P3 V1 1.5")
rn.clear
assert_equal(rn.to_s, "")
end
def test_2_Destroy
rn = RBA::RNetwork::new
a = rn.create_node(RBA::RNode::VertexPort, 1)
b = rn.create_node(RBA::RNode::Internal, 2)
rab = rn.create_element(1.0, a, b)
# this should invalid the pointers
rn._destroy
begin
assert_equal(a.to_s, "")
rescue => ex
# graph has been destroyed already
end
begin
assert_equal(rab.to_s, "")
rescue => ex
# graph has been destroyed already
end
end
def test_3_SQC
poly = RBA::Polygon::new(RBA::Box::new(0, 0, 1100, 100))
vp = [ RBA::Point::new(50, 50) ]
pp = [ RBA::Polygon::new(RBA::Box::new(1000, 0, 1100, 100)) ]
rex = RBA::RExtractor::square_counting_extractor(0.001)
rn = rex.extract(poly, vp, pp)
assert_equal(rn.to_s, "R P0 V0 10")
end
def test_3_TX
poly = RBA::Polygon::new(RBA::Box::new(0, 0, 1100, 100))
vp = [ RBA::Point::new(50, 50) ]
pp = [ RBA::Polygon::new(RBA::Box::new(1000, 0, 1100, 100)) ]
rex = RBA::RExtractor::tesselation_extractor(0.001, 0.8)
rn = rex.extract(poly, vp, pp)
assert_equal(rn.to_s, "R P0 V0 9.44")
end
def test_4_ExtractorTech
l1 = 1
l2 = 2
l3 = 3
tech = RBA::RExtractorTech::new
tech.skip_simplify = true
assert_equal(tech.skip_simplify, true)
tech.skip_simplify = false
assert_equal(tech.skip_simplify, false)
via1 = RBA::RExtractorTechVia::new
via1.bottom_conductor = l1
via1.cut_layer = l2
via1.top_conductor = l3
via1.resistance = 2.0
via1.merge_distance = 0.2
assert_equal(via1.to_s, "Via(bottom=L1, cut=L2, top=L3, R=2 µm²*Ohm, d_merge=0.2 µm)")
assert_equal(via1.bottom_conductor, l1)
assert_equal(via1.cut_layer, l2)
assert_equal(via1.top_conductor, l3)
assert_equal(via1.resistance, 2.0)
assert_equal(via1.merge_distance.to_s, "0.2")
tech.add_via(via1)
assert_equal(tech.each_via.collect { |v| v.cut_layer }, [ l2 ])
tech.clear_vias
assert_equal(tech.each_via.collect { |v| v.cut_layer }, [])
tech.add_via(via1)
assert_equal(tech.each_via.collect { |v| v.cut_layer }, [ l2 ])
cond1 = RBA::RExtractorTechConductor::new
cond1.layer = l1
cond1.resistance = 0.5
assert_equal(cond1.to_s, "Conductor(layer=L1, R=0.5 Ohm/sq, algo=SquareCounting)")
assert_equal(cond1.layer, l1)
assert_equal(cond1.resistance, 0.5)
cond2 = RBA::RExtractorTechConductor::new
cond2.layer = l3
cond2.resistance = 0.25
tech.add_conductor(cond2)
assert_equal(tech.each_conductor.collect { |c| c.layer }, [ l3 ])
assert_equal(tech.to_s,
"Via(bottom=L1, cut=L2, top=L3, R=2 µm²*Ohm, d_merge=0.2 µm)\n" +
"Conductor(layer=L3, R=0.25 Ohm/sq, algo=SquareCounting)"
)
tech.clear_conductors
assert_equal(tech.each_conductor.collect { |c| c.layer }, [])
tech.add_conductor(cond1)
tech.add_conductor(cond2)
assert_equal(tech.each_conductor.collect { |c| c.layer }, [ l1, l3 ])
end
# A complete, small example for a R network extraction
def test_5_NetEx
ly = RBA::Layout::new
ly.read(File.join($ut_testsrc, "testdata", "pex", "netex_test1.gds"))
rex = RBA::RNetExtractor::new(ly.dbu)
tc = ly.top_cell
l1 = ly.layer(1, 0)
l1p = ly.layer(1, 1)
l1v = ly.layer(1, 2)
l2 = ly.layer(2, 0)
l3 = ly.layer(3, 0)
l3p = ly.layer(3, 1)
l3v = ly.layer(3, 2)
# That is coincidence, but it needs to be that way for the strings to match
assert_equal(l1, 1)
assert_equal(l2, 0)
assert_equal(l3, 2)
geo = {}
[ l1, l2, l3 ].each do |l|
geo[l] = RBA::Region::new(tc.begin_shapes_rec(l))
end
tech = RBA::RExtractorTech::new
via1 = RBA::RExtractorTechVia::new
via1.bottom_conductor = l1
via1.cut_layer = l2
via1.top_conductor = l3
via1.resistance = 2.0
via1.merge_distance = 0.2
tech.add_via(via1)
cond1 = RBA::RExtractorTechConductor::new
cond1.layer = l1
cond1.resistance = 0.5
cond2 = RBA::RExtractorTechConductor::new
cond2.layer = l3
cond2.resistance = 0.25
tech.add_conductor(cond1)
tech.add_conductor(cond2)
polygon_ports = { }
polygon_ports[l1] = RBA::Region::new(tc.begin_shapes_rec(l1p)).each_merged.to_a
polygon_ports[l3] = RBA::Region::new(tc.begin_shapes_rec(l3p)).each_merged.to_a
vertex_ports = { }
vertex_ports[l1] = RBA::Region::new(tc.begin_shapes_rec(l1v)).each_merged.collect { |p| p.bbox.center }
vertex_ports[l3] = RBA::Region::new(tc.begin_shapes_rec(l3v)).each_merged.collect { |p| p.bbox.center }
network = rex.extract(tech, geo, vertex_ports, polygon_ports)
n = network.to_s(true)
n = n.gsub(/ \$\d+\./, " $x.")
n = n.split("\n").sort.join("\n") + "\n"
assert_equal(n, <<"END")
R $x.1(9.3,-5.9;9.9,-5.3) $x.2(10,-3.5;10,-2.7) 13.2813
R $x.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25
R $x.1(9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75
R $x.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1
R $x.2(10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.7812
R V0.1(5.2,0.4;5.2,0.4) V0.2(0.3,-5.7;0.5,-5.5) 17.375
END
end
end
load("test_epilogue.rb")