diff --git a/scripts/make_stubs.sh b/scripts/make_stubs.sh index e9f5bad03..68e39f1c2 100755 --- a/scripts/make_stubs.sh +++ b/scripts/make_stubs.sh @@ -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 diff --git a/setup.py b/setup.py index 04fb5c4f9..df2b859fd 100644 --- a/setup.py +++ b/setup.py @@ -592,6 +592,25 @@ def version(self): ) 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 @@ def version(self): 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 @@ def version(self): 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} ) diff --git a/src/buddies/src/bd/bd.pro b/src/buddies/src/bd/bd.pro index ef71f239e..2deeb870f 100644 --- a/src/buddies/src/bd/bd.pro +++ b/src/buddies/src/bd/bd.pro @@ -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 diff --git a/src/buddies/src/bd/strmrun.cc b/src/buddies/src/bd/strmrun.cc index d13abefe7..de42220c0 100644 --- a/src/buddies/src/bd/strmrun.cc +++ b/src/buddies/src/bd/strmrun.cc @@ -35,6 +35,7 @@ #include "gsiExpression.h" #include "libForceLink.h" #include "rdbForceLink.h" +#include "pexForceLink.h" #include "lymMacro.h" #include "lymMacroCollection.h" diff --git a/src/buddies/src/buddy_app.pri b/src/buddies/src/buddy_app.pri index da47c2fa2..58da418bf 100644 --- a/src/buddies/src/buddy_app.pri +++ b/src/buddies/src/buddy_app.pri @@ -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 diff --git a/src/db/db/db.pro b/src/db/db/db.pro index bbae9671a..295976015 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -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 \ diff --git a/src/db/db/dbMutableRegion.cc b/src/db/db/dbMutableRegion.cc index fef42681a..aa1a6b3a2 100644 --- a/src/db/db/dbMutableRegion.cc +++ b/src/db/db/dbMutableRegion.cc @@ -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 ()); } } diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc new file mode 100644 index 000000000..07b7b8fa5 --- /dev/null +++ b/src/db/db/dbPLC.cc @@ -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 +#include +#include +#include + +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 (*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 (); + } + 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 & +Vertex::ids () const +{ + if (mp_ids != 0) { + return *mp_ids; + } else { + static std::set empty; + return empty; + } +} + +std::vector +Vertex::polygons () const +{ + std::set seen; + std::vector 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 ¢er, 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 e; + e.swap (mp_e); + + std::multimap 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 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 +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 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 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 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 diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h new file mode 100644 index 000000000..6a65e61d1 --- /dev/null +++ b/src/db/db/dbPLC.h @@ -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 +#include +#include +#include + +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 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 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 &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 ¢er, 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 ¢er, 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; + + void remove_edge (const edges_iterator_non_const &ec) + { + mp_edges.erase (ec); + } + + Graph *mp_graph; + edges_type mp_edges; + std::set *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; + + 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, 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 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 + 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 mp_e; + std::vector 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 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 + 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 mp_polygons; + tl::stable_vector m_edges_heap; + std::vector m_returned_edges; + tl::stable_vector m_vertex_heap; + size_t m_id; + + tl::list &polygons () { return mp_polygons; } + tl::stable_vector &edges () { return m_edges_heap; } + tl::stable_vector &vertexes () { return m_vertex_heap; } +}; + +} // namespace plc + +} // namespace db + +#endif + diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc new file mode 100644 index 000000000..426dfa06d --- /dev/null +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -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 +#include +#include +#include + +namespace db +{ + +namespace plc +{ + +ConvexDecomposition::ConvexDecomposition (Graph *graph) +{ + mp_graph = graph; + clear (); +} + +void +ConvexDecomposition::clear () +{ + mp_graph->clear (); +} + +struct SortAngleAndEdgesByEdgeLength +{ + typedef std::list > 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 +struct less_compare_func +{ + bool operator() (const T &a, const T &b) const + { + return a.less (b); + } +}; + +// TODO: move to some generic header +template +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 &concave_vertexes) +{ + concave_vertexes.clear (); + + std::unordered_set 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 +ConvexDecomposition::search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction) +{ + auto vtri = v0->polygons (); // TODO: slow? + std::vector 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 ¶m) +{ + bool with_segments = param.with_segments; + bool split_edges = param.split_edges; + + std::vector concave_vertexes; + collect_concave_vertexes (concave_vertexes); + + std::vector 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 ()); + new_points.erase (std::unique (new_points.begin (), new_points.end (), equal_compare_func ()), 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 essential_edges; + + typedef std::list > angles_and_edges_list; + angles_and_edges_list angles_and_edges; + std::vector 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 left_triangles; + for (auto it = mp_graph->begin (); it != mp_graph->end (); ++it) { + if (! it->is_outside ()) { + left_triangles.insert (it.operator-> ()); + } + } + + std::list > polygons; + std::list > internal_vertexes; + + while (! left_triangles.empty ()) { + + polygons.push_back (std::unordered_set ()); + std::unordered_set &edges = polygons.back (); + + internal_vertexes.push_back (std::unordered_set ()); + std::unordered_set &ivs = internal_vertexes.back (); + + const Polygon *tri = *left_triangles.begin (); + std::vector 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 (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 ¶meters, double dbu) +{ + decompose (poly, parameters, db::CplxTrans (dbu)); +} + +void +ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, double dbu) +{ + decompose (poly, vertexes, parameters, db::CplxTrans (dbu)); +} + +void +ConvexDecomposition::decompose (const db::Polygon &poly, const ConvexDecompositionParameters ¶meters, 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 &vertexes, const ConvexDecompositionParameters ¶meters, 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 ¶meters, 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 &vertexes, const ConvexDecompositionParameters ¶meters, 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 ®ion, const ConvexDecompositionParameters ¶meters, double dbu) +{ + decompose (region, parameters, db::CplxTrans (dbu)); +} + +void +ConvexDecomposition::decompose (const db::Region ®ion, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans) +{ + Triangulation tri (mp_graph); + tri.triangulate (region, parameters.tri_param, trans); + + hertel_mehlhorn_decomposition (tri, parameters); +} + +} // namespace plc + +} // namespace db diff --git a/src/db/db/dbPLCConvexDecomposition.h b/src/db/db/dbPLCConvexDecomposition.h new file mode 100644 index 000000000..6b0effc43 --- /dev/null +++ b/src/db/db/dbPLCConvexDecomposition.h @@ -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 +#include +#include +#include + +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 ®ion, const ConvexDecompositionParameters ¶meters, double dbu = 1.0); + + // more versions + void decompose (const db::Region ®ion, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void decompose (const db::Polygon &poly, const ConvexDecompositionParameters ¶meters, double dbu = 1.0); + void decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, double dbu = 1.0); + void decompose (const db::Polygon &poly, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Decomposes a floating-point polygon + */ + void decompose (const db::DPolygon &poly, const ConvexDecompositionParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); + void decompose (const db::DPolygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, 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 ¶m); + void collect_concave_vertexes (std::vector &concave_vertexes); + std::pair search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction); +}; + +} // namespace plc + +} // namespace db + +#endif + diff --git a/src/db/db/dbTriangles.cc b/src/db/db/dbPLCTriangulation.cc similarity index 60% rename from src/db/db/dbTriangles.cc rename to src/db/db/dbPLCTriangulation.cc index e712abaa2..c1453f3dc 100644 --- a/src/db/db/dbTriangles.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -21,10 +21,7 @@ */ -#include "dbTriangles.h" -#include "dbLayout.h" -#include "dbWriter.h" -#include "tlStream.h" +#include "dbPLCTriangulation.h" #include "tlLog.h" #include "tlTimer.h" @@ -36,138 +33,72 @@ namespace db { +namespace plc +{ + static inline bool is_equal (const db::DPoint &a, const db::DPoint &b) { return std::abs (a.x () - b.x ()) < std::max (1.0, (std::abs (a.x ()) + std::abs (b.x ()))) * db::epsilon && std::abs (a.y () - b.y ()) < std::max (1.0, (std::abs (a.y ()) + std::abs (b.y ()))) * db::epsilon; } -Triangles::Triangles () - : m_is_constrained (false), m_level (0), m_id (0), m_flips (0), m_hops (0) -{ - // .. nothing yet .. -} - -Triangles::~Triangles () +Triangulation::Triangulation (Graph *graph) { + mp_graph = graph; clear (); } -db::Vertex * -Triangles::create_vertex (double x, double y) -{ - m_vertex_heap.push_back (db::Vertex (x, y)); - return &m_vertex_heap.back (); -} - -db::Vertex * -Triangles::create_vertex (const db::DPoint &pt) -{ - m_vertex_heap.push_back (pt); - return &m_vertex_heap.back (); -} - -db::TriangleEdge * -Triangles::create_edge (db::Vertex *v1, db::Vertex *v2) -{ - db::TriangleEdge *edge = 0; - - if (! m_returned_edges.empty ()) { - edge = m_returned_edges.back (); - m_returned_edges.pop_back (); - *edge = db::TriangleEdge (v1, v2); - } else { - m_edges_heap.push_back (db::TriangleEdge (v1, v2)); - edge = &m_edges_heap.back (); - } - - edge->link (); - edge->set_id (++m_id); - return edge; -} - -db::Triangle * -Triangles::create_triangle (TriangleEdge *e1, TriangleEdge *e2, TriangleEdge *e3) -{ - db::Triangle *res = new db::Triangle (e1, e2, e3); - res->set_id (++m_id); - mp_triangles.push_back (res); - - return res; -} - void -Triangles::remove_triangle (db::Triangle *tri) +Triangulation::clear () { - db::TriangleEdge *edges [3]; - for (int i = 0; i < 3; ++i) { - edges [i] = tri->edge (i); - } - - delete tri; + mp_graph->clear (); - // clean up edges we do no longer need - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = edges [i]; - if (e && e->left () == 0 && e->right () == 0 && e->v1 ()) { - e->unlink (); - m_returned_edges.push_back (e); - } - } + m_is_constrained = false; + m_level = 0; + m_id = 0; + m_flips = m_hops = 0; } void -Triangles::init_box (const db::DBox &box) +Triangulation::init_box (const db::DBox &box) { double xmin = box.left (), xmax = box.right (); double ymin = box.bottom (), ymax = box.top (); - db::Vertex *vbl = create_vertex (xmin, ymin); - db::Vertex *vtl = create_vertex (xmin, ymax); - db::Vertex *vbr = create_vertex (xmax, ymin); - db::Vertex *vtr = create_vertex (xmax, ymax); + Vertex *vbl = mp_graph->create_vertex (xmin, ymin); + Vertex *vtl = mp_graph->create_vertex (xmin, ymax); + Vertex *vbr = mp_graph->create_vertex (xmax, ymin); + Vertex *vtr = mp_graph->create_vertex (xmax, ymax); - db::TriangleEdge *sl = create_edge (vbl, vtl); - db::TriangleEdge *sd = create_edge (vtl, vbr); - db::TriangleEdge *sb = create_edge (vbr, vbl); + Edge *sl = mp_graph->create_edge (vbl, vtl); + Edge *sd = mp_graph->create_edge (vtl, vbr); + Edge *sb = mp_graph->create_edge (vbr, vbl); - db::TriangleEdge *sr = create_edge (vbr, vtr); - db::TriangleEdge *st = create_edge (vtr, vtl); + Edge *sr = mp_graph->create_edge (vbr, vtr); + Edge *st = mp_graph->create_edge (vtr, vtl); - create_triangle (sl, sd, sb); - create_triangle (sd, sr, st); + mp_graph->create_triangle (sl, sd, sb); + mp_graph->create_triangle (sd, sr, st); } -std::string -Triangles::to_string () +bool +Triangulation::check (bool check_delaunay) const { - std::string res; - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - if (! res.empty ()) { - res += ", "; + bool res = true; + + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { + if (t->size () != 3) { + res = false; + tl::error << "(check error) not a triangle: " << t->to_string (); } - res += t->to_string (); } - return res; -} -db::DBox -Triangles::bbox () const -{ - db::DBox box; - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - box += t->bbox (); + if (! res) { + return false; } - return box; -} - -bool -Triangles::check (bool check_delaunay) const -{ - bool res = true; if (check_delaunay) { - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { auto cp = t->circumcircle (); auto vi = find_inside_circle (cp.first, cp.second); if (! vi.empty ()) { @@ -180,9 +111,9 @@ Triangles::check (bool check_delaunay) const } } - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { for (int i = 0; i < 3; ++i) { - if (! t->edge (i)->has_triangle (t.operator-> ())) { + if (! t->edge (i)->has_polygon (t.operator-> ())) { tl::error << "(check error) edges " << t->edge (i)->to_string (true) << " attached to triangle " << t->to_string (true) << " does not refer to this triangle"; res = false; @@ -190,7 +121,7 @@ Triangles::check (bool check_delaunay) const } } - for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { + for (auto e = mp_graph->edges ().begin (); e != mp_graph->edges ().end (); ++e) { if (!e->left () && !e->right ()) { continue; @@ -203,7 +134,7 @@ Triangles::check (bool check_delaunay) const } } - for (auto t = e->begin_triangles (); t != e->end_triangles (); ++t) { + for (auto t = e->begin_polygons (); t != e->end_polygons (); ++t) { if (! t->has_edge (e.operator-> ())) { tl::error << "(check error) edge " << e->to_string (true) << " not found in adjacent triangle " << t->to_string (true); res = false; @@ -216,7 +147,7 @@ Triangles::check (bool check_delaunay) const tl::error << "(check error) edges " << e->to_string (true) << " vertex 2 not found in adjacent triangle " << t->to_string (true); res = false; } - db::Vertex *vopp = t->opposite (e.operator-> ()); + Vertex *vopp = t->opposite (e.operator-> ()); double sgn = (e->left () == t.operator-> ()) ? 1.0 : -1.0; double vp = db::vprod (e->d(), *vopp - *e->v1 ()); // positive if on left side if (vp * sgn <= 0.0) { @@ -237,7 +168,7 @@ Triangles::check (bool check_delaunay) const } - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { unsigned int num_outside_edges = 0; for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { if ((*e)->is_outside ()) { @@ -258,81 +189,21 @@ Triangles::check (bool check_delaunay) const return res; } -db::Layout * -Triangles::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)); - - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - db::DPoint pts[3]; - for (int i = 0; i < 3; ++i) { - pts[i] = *t->vertex (i); - } - db::DPolygon poly; - poly.assign_hull (pts + 0, pts + 3); - 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 -Triangles::dump (const std::string &path, bool decompose_by_id) const +std::vector +Triangulation::find_points_around (Vertex *vertex, double radius) { - std::unique_ptr ly (to_layout (decompose_by_id)); - - tl::OutputStream stream (path); - - db::SaveLayoutOptions opt; - db::Writer writer (opt); - writer.write (*ly, stream); - - tl::info << "Triangles written to " << path; -} - -std::vector -Triangles::find_points_around (db::Vertex *vertex, double radius) -{ - std::set seen; + std::set seen; seen.insert (vertex); - std::vector res; - std::vector new_vertexes, next_vertexes; + std::vector res; + std::vector new_vertexes, next_vertexes; new_vertexes.push_back (vertex); while (! new_vertexes.empty ()) { next_vertexes.clear (); for (auto v = new_vertexes.begin (); v != new_vertexes.end (); ++v) { for (auto e = (*v)->begin_edges (); e != (*v)->end_edges (); ++e) { - db::Vertex *ov = (*e)->other (*v); + Vertex *ov = (*e)->other (*v); if (ov->in_circle (*vertex, radius) == 1 && seen.insert (ov).second) { next_vertexes.push_back (ov); res.push_back (ov); @@ -345,22 +216,22 @@ Triangles::find_points_around (db::Vertex *vertex, double radius) return res; } -db::Vertex * -Triangles::insert_point (const db::DPoint &point, std::list > *new_triangles) +Vertex * +Triangulation::insert_point (const db::DPoint &point, std::list > *new_triangles) { - return insert (create_vertex (point), new_triangles); + return insert (mp_graph->create_vertex (point), new_triangles); } -db::Vertex * -Triangles::insert_point (db::DCoord x, db::DCoord y, std::list > *new_triangles) +Vertex * +Triangulation::insert_point (db::DCoord x, db::DCoord y, std::list > *new_triangles) { - return insert (create_vertex (x, y), new_triangles); + return insert (mp_graph->create_vertex (x, y), new_triangles); } -db::Vertex * -Triangles::insert (db::Vertex *vertex, std::list > *new_triangles) +Vertex * +Triangulation::insert (Vertex *vertex, std::list > *new_triangles) { - std::vector tris = find_triangle_for_point (*vertex); + std::vector tris = find_triangle_for_point (*vertex); // the new vertex is outside the domain if (tris.empty ()) { @@ -370,10 +241,10 @@ Triangles::insert (db::Vertex *vertex, std::list > *n } // check, if the new vertex is on an edge (may be edge between triangles or edge on outside) - std::vector on_edges; - std::vector on_vertex; + std::vector on_edges; + std::vector on_vertex; for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = tris.front ()->edge (i); + Edge *e = tris.front ()->edge (i); if (e->side_of (*vertex) == 0) { if (is_equal (*vertex, *e->v1 ()) || is_equal (*vertex, *e->v2 ())) { on_vertex.push_back (e); @@ -405,14 +276,14 @@ Triangles::insert (db::Vertex *vertex, std::list > *n tl_assert (false); } -std::vector -Triangles::find_triangle_for_point (const db::DPoint &point) +std::vector +Triangulation::find_triangle_for_point (const db::DPoint &point) { - db::TriangleEdge *edge = find_closest_edge (point); + Edge *edge = find_closest_edge (point); - std::vector res; + std::vector res; if (edge) { - for (auto t = edge->begin_triangles (); t != edge->end_triangles (); ++t) { + for (auto t = edge->begin_polygons (); t != edge->end_polygons (); ++t) { if (t->contains (point) >= 0) { res.push_back (t.operator-> ()); } @@ -422,21 +293,21 @@ Triangles::find_triangle_for_point (const db::DPoint &point) return res; } -db::TriangleEdge * -Triangles::find_closest_edge (const db::DPoint &p, db::Vertex *vstart, bool inside_only) +Edge * +Triangulation::find_closest_edge (const db::DPoint &p, Vertex *vstart, bool inside_only) const { if (!vstart) { - if (! mp_triangles.empty ()) { + if (! mp_graph->polygons ().empty ()) { unsigned int ls = 0; - size_t n = m_vertex_heap.size (); + size_t n = mp_graph->vertexes ().size (); size_t m = n; // A simple heuristics that takes a sqrt(N) sample from the // vertexes to find a good starting point - vstart = mp_triangles.begin ()->vertex (0); + vstart = mp_graph->polygons ().begin ()->vertex (0); double dmin = vstart->distance (p); while (ls * ls < m) { @@ -444,7 +315,7 @@ Triangles::find_closest_edge (const db::DPoint &p, db::Vertex *vstart, bool insi for (size_t i = m / 2; i < n; i += m) { ++ls; // NOTE: this assumes the heap is not too loaded with orphan vertexes - db::Vertex *v = (m_vertex_heap.begin () + i).operator-> (); + Vertex *v = (mp_graph->vertexes ().begin () + i).operator-> (); if (v->begin_edges () != v->end_edges ()) { double d = v->distance (p); if (d < dmin) { @@ -466,12 +337,12 @@ Triangles::find_closest_edge (const db::DPoint &p, db::Vertex *vstart, bool insi db::DEdge line (*vstart, p); double d = -1.0; - db::TriangleEdge *edge = 0; - db::Vertex *v = vstart; + Edge *edge = 0; + Vertex *v = vstart; while (v) { - db::Vertex *vnext = 0; + Vertex *vnext = 0; for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { @@ -499,7 +370,7 @@ Triangles::find_closest_edge (const db::DPoint &p, db::Vertex *vstart, bool insi // this differentiation selects the edge which bends further towards // the target point if both edges share a common point and that // is the one the determines the distance. - db::Vertex *cv = edge->common_vertex (*e); + Vertex *cv = edge->common_vertex (*e); if (cv) { db::DVector edge_d = *edge->other (cv) - *cv; db::DVector e_d = *(*e)->other(cv) - *cv; @@ -532,29 +403,29 @@ Triangles::find_closest_edge (const db::DPoint &p, db::Vertex *vstart, bool insi } void -Triangles::insert_new_vertex (db::Vertex *vertex, std::list > *new_triangles_out) +Triangulation::insert_new_vertex (Vertex *vertex, std::list > *new_triangles_out) { - if (mp_triangles.empty ()) { + if (mp_graph->polygons ().empty ()) { - tl_assert (m_vertex_heap.size () <= size_t (3)); // fails if vertexes were created but not inserted. + tl_assert (mp_graph->vertexes ().size () <= size_t (3)); // fails if vertexes were created but not inserted. - if (m_vertex_heap.size () == 3) { + if (mp_graph->vertexes ().size () == 3) { - std::vector vv; - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { + std::vector vv; + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { vv.push_back (v.operator-> ()); } // form the first triangle - db::TriangleEdge *s1 = create_edge (vv[0], vv[1]); - db::TriangleEdge *s2 = create_edge (vv[1], vv[2]); - db::TriangleEdge *s3 = create_edge (vv[2], vv[0]); + Edge *s1 = mp_graph->create_edge (vv[0], vv[1]); + Edge *s2 = mp_graph->create_edge (vv[1], vv[2]); + Edge *s3 = mp_graph->create_edge (vv[2], vv[0]); if (db::vprod_sign (s1->d (), s2->d ()) == 0) { // avoid degenerate Triangles to happen here tl_assert (false); } else { - db::Triangle *t = create_triangle (s1, s2, s3); + Polygon *t = mp_graph->create_triangle (s1, s2, s3); if (new_triangles_out) { new_triangles_out->push_back (t); } @@ -566,16 +437,16 @@ Triangles::insert_new_vertex (db::Vertex *vertex, std::list new_triangles; + std::vector new_triangles; // Find closest edge - db::TriangleEdge *closest_edge = find_closest_edge (*vertex); + Edge *closest_edge = find_closest_edge (*vertex); tl_assert (closest_edge != 0); - db::TriangleEdge *s1 = create_edge (vertex, closest_edge->v1 ()); - db::TriangleEdge *s2 = create_edge (vertex, closest_edge->v2 ()); + Edge *s1 = mp_graph->create_edge (vertex, closest_edge->v1 ()); + Edge *s2 = mp_graph->create_edge (vertex, closest_edge->v2 ()); - db::Triangle *t = create_triangle (s1, closest_edge, s2); + Polygon *t = mp_graph->create_triangle (s1, closest_edge, s2); new_triangles.push_back (t); add_more_triangles (new_triangles, closest_edge, closest_edge->v1 (), vertex, s1); @@ -585,18 +456,18 @@ Triangles::insert_new_vertex (db::Vertex *vertex, std::listinsert (new_triangles_out->end (), new_triangles.begin (), new_triangles.end ()); } - fix_triangles (new_triangles, std::vector (), new_triangles_out); + fix_triangles (new_triangles, std::vector (), new_triangles_out); } void -Triangles::add_more_triangles (std::vector &new_triangles, - db::TriangleEdge *incoming_edge, - db::Vertex *from_vertex, db::Vertex *to_vertex, - db::TriangleEdge *conn_edge) +Triangulation::add_more_triangles (std::vector &new_triangles, + Edge *incoming_edge, + Vertex *from_vertex, Vertex *to_vertex, + Edge *conn_edge) { while (true) { - db::TriangleEdge *next_edge = 0; + Edge *next_edge = 0; for (auto e = from_vertex->begin_edges (); e != from_vertex->end_edges (); ++e) { if (! (*e)->has_vertex (to_vertex) && (*e)->is_outside ()) { @@ -607,16 +478,16 @@ Triangles::add_more_triangles (std::vector &new_triangles, } tl_assert (next_edge != 0); - db::Vertex *next_vertex = next_edge->other (from_vertex); + Vertex *next_vertex = next_edge->other (from_vertex); db::DVector d_from_to = *to_vertex - *from_vertex; - db::Vertex *incoming_vertex = incoming_edge->other (from_vertex); + Vertex *incoming_vertex = incoming_edge->other (from_vertex); if (db::vprod_sign(*from_vertex - *incoming_vertex, d_from_to) * db::vprod_sign(*from_vertex - *next_vertex, d_from_to) >= 0) { return; } - db::TriangleEdge *next_conn_edge = create_edge (next_vertex, to_vertex); - db::Triangle *t = create_triangle (next_conn_edge, next_edge, conn_edge); + Edge *next_conn_edge = mp_graph->create_edge (next_vertex, to_vertex); + Polygon *t = mp_graph->create_triangle (next_conn_edge, next_edge, conn_edge); new_triangles.push_back (t); incoming_edge = next_edge; @@ -627,23 +498,23 @@ Triangles::add_more_triangles (std::vector &new_triangles, } void -Triangles::split_triangle (db::Triangle *t, db::Vertex *vertex, std::list > *new_triangles_out) +Triangulation::split_triangle (Polygon *t, Vertex *vertex, std::list > *new_triangles_out) { t->unlink (); - std::map v2new_edges; - std::vector new_edges; + std::map v2new_edges; + std::vector new_edges; for (int i = 0; i < 3; ++i) { - db::Vertex *v = t->vertex (i); - db::TriangleEdge *e = create_edge (v, vertex); + Vertex *v = t->vertex (i); + Edge *e = mp_graph->create_edge (v, vertex); v2new_edges[v] = e; new_edges.push_back (e); } - std::vector new_triangles; + std::vector new_triangles; for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = t->edge (i); - db::Triangle *new_triangle = create_triangle (e, v2new_edges[e->v1 ()], v2new_edges[e->v2 ()]); + Edge *e = t->edge (i); + Polygon *new_triangle = mp_graph->create_triangle (e, v2new_edges[e->v1 ()], v2new_edges[e->v2 ()]); if (new_triangles_out) { new_triangles_out->push_back (new_triangle); } @@ -651,24 +522,24 @@ Triangles::split_triangle (db::Triangle *t, db::Vertex *vertex, std::listremove_polygon (t); fix_triangles (new_triangles, new_edges, new_triangles_out); } void -Triangles::split_triangles_on_edge (db::Vertex *vertex, db::TriangleEdge *split_edge, std::list > *new_triangles_out) +Triangulation::split_triangles_on_edge (Vertex *vertex, Edge *split_edge, std::list > *new_triangles_out) { - TriangleEdge *s1 = create_edge (split_edge->v1 (), vertex); - TriangleEdge *s2 = create_edge (split_edge->v2 (), vertex); + Edge *s1 = mp_graph->create_edge (split_edge->v1 (), vertex); + Edge *s2 = mp_graph->create_edge (split_edge->v2 (), vertex); s1->set_is_segment (split_edge->is_segment ()); s2->set_is_segment (split_edge->is_segment ()); - std::vector new_triangles; + std::vector new_triangles; - std::vector tris; + std::vector tris; tris.reserve (2); - for (auto t = split_edge->begin_triangles (); t != split_edge->end_triangles (); ++t) { + for (auto t = split_edge->begin_polygons (); t != split_edge->end_polygons (); ++t) { tris.push_back (t.operator-> ()); } @@ -676,16 +547,16 @@ Triangles::split_triangles_on_edge (db::Vertex *vertex, db::TriangleEdge *split_ (*t)->unlink (); - db::Vertex *ext_vertex = (*t)->opposite (split_edge); - TriangleEdge *new_edge = create_edge (ext_vertex, vertex); + Vertex *ext_vertex = (*t)->opposite (split_edge); + Edge *new_edge = mp_graph->create_edge (ext_vertex, vertex); for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = (*t)->edge (i); + Edge *e = (*t)->edge (i); if (e->has_vertex (ext_vertex)) { - TriangleEdge *partial = e->has_vertex (split_edge->v1 ()) ? s1 : s2; - db::Triangle *new_triangle = create_triangle (new_edge, partial, e); + Edge *partial = e->has_vertex (split_edge->v1 ()) ? s1 : s2; + Polygon *new_triangle = mp_graph->create_triangle (new_edge, partial, e); if (new_triangles_out) { new_triangles_out->push_back (new_triangle); @@ -700,39 +571,39 @@ Triangles::split_triangles_on_edge (db::Vertex *vertex, db::TriangleEdge *split_ } for (auto t = tris.begin (); t != tris.end (); ++t) { - remove_triangle (*t); + mp_graph->remove_polygon (*t); } - std::vector fixed_edges; + std::vector fixed_edges; fixed_edges.push_back (s1); fixed_edges.push_back (s2); fix_triangles (new_triangles, fixed_edges, new_triangles_out); } -std::vector -Triangles::find_touching (const db::DBox &box) const +std::vector +Triangulation::find_touching (const db::DBox &box) const { // NOTE: this is a naive, slow implementation for test purposes - std::vector res; - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { + std::vector res; + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { if (v->begin_edges () != v->end_edges ()) { if (box.contains (*v)) { - res.push_back (const_cast (v.operator-> ())); + res.push_back (const_cast (v.operator-> ())); } } } return res; } -std::vector -Triangles::find_inside_circle (const db::DPoint ¢er, double radius) const +std::vector +Triangulation::find_inside_circle (const db::DPoint ¢er, double radius) const { // NOTE: this is a naive, slow implementation for test purposes - std::vector res; - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { + std::vector res; + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { if (v->begin_edges () != v->end_edges ()) { if (v->in_circle (center, radius) == 1) { - res.push_back (const_cast (v.operator-> ())); + res.push_back (const_cast (v.operator-> ())); } } } @@ -740,7 +611,7 @@ Triangles::find_inside_circle (const db::DPoint ¢er, double radius) const } void -Triangles::remove (db::Vertex *vertex, std::list > *new_triangles) +Triangulation::remove (Vertex *vertex, std::list > *new_triangles) { if (vertex->begin_edges () == vertex->end_edges ()) { // removing an orphan vertex -> ignore @@ -752,11 +623,11 @@ Triangles::remove (db::Vertex *vertex, std::list > *n } void -Triangles::remove_outside_vertex (db::Vertex *vertex, std::list > *new_triangles_out) +Triangulation::remove_outside_vertex (Vertex *vertex, std::list > *new_triangles_out) { - auto to_remove = vertex->triangles (); + auto to_remove = vertex->polygons (); - std::vector outer_edges; + std::vector outer_edges; for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { outer_edges.push_back ((*t)->opposite (vertex)); } @@ -768,22 +639,22 @@ Triangles::remove_outside_vertex (db::Vertex *vertex, std::listremove_polygon (*t); } - fix_triangles (new_triangles, std::vector (), new_triangles_out); + fix_triangles (new_triangles, std::vector (), new_triangles_out); } void -Triangles::remove_inside_vertex (db::Vertex *vertex, std::list > *new_triangles_out) +Triangulation::remove_inside_vertex (Vertex *vertex, std::list > *new_triangles_out) { - std::set triangles_to_fix; + std::set triangles_to_fix; bool make_new_triangle = true; while (vertex->num_edges (4) > 3) { - db::TriangleEdge *to_flip = 0; + Edge *to_flip = 0; for (auto e = vertex->begin_edges (); e != vertex->end_edges () && to_flip == 0; ++e) { if ((*e)->can_flip ()) { to_flip = *e; @@ -809,7 +680,7 @@ Triangles::remove_inside_vertex (db::Vertex *vertex, std::listbegin_edges (); e != vertex->end_edges () && !jseg; ++e) { if ((*e)->can_join_via (vertex)) { jseg = *e; @@ -817,24 +688,24 @@ Triangles::remove_inside_vertex (db::Vertex *vertex, std::listleft ()->opposite (jseg); - db::TriangleEdge *s1 = jseg->left ()->opposite (vertex); - db::Vertex *v2 = jseg->right ()->opposite (jseg); - db::TriangleEdge *s2 = jseg->right ()->opposite (vertex); + Vertex *v1 = jseg->left ()->opposite (jseg); + Edge *s1 = jseg->left ()->opposite (vertex); + Vertex *v2 = jseg->right ()->opposite (jseg); + Edge *s2 = jseg->right ()->opposite (vertex); - db::TriangleEdge *jseg_opp = 0; + Edge *jseg_opp = 0; for (auto e = vertex->begin_edges (); e != vertex->end_edges () && !jseg_opp; ++e) { - if (!(*e)->has_triangle (jseg->left ()) && !(*e)->has_triangle (jseg->right ())) { + if (!(*e)->has_polygon (jseg->left ()) && !(*e)->has_polygon (jseg->right ())) { jseg_opp = *e; } } - db::TriangleEdge *s1opp = jseg_opp->left ()->opposite (vertex); - db::TriangleEdge *s2opp = jseg_opp->right ()->opposite (vertex); + Edge *s1opp = jseg_opp->left ()->opposite (vertex); + Edge *s2opp = jseg_opp->right ()->opposite (vertex); - db::TriangleEdge *new_edge = create_edge (v1, v2); - db::Triangle *t1 = create_triangle (s1, s2, new_edge); - db::Triangle *t2 = create_triangle (s1opp, s2opp, new_edge); + Edge *new_edge = mp_graph->create_edge (v1, v2); + Polygon *t1 = mp_graph->create_triangle (s1, s2, new_edge); + Polygon *t2 = mp_graph->create_triangle (s1opp, s2opp, new_edge); triangles_to_fix.insert (t1); triangles_to_fix.insert (t2); @@ -843,9 +714,9 @@ Triangles::remove_inside_vertex (db::Vertex *vertex, std::listtriangles (); + auto to_remove = vertex->polygons (); - std::vector outer_edges; + std::vector outer_edges; for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { outer_edges.push_back ((*t)->opposite (vertex)); } @@ -854,14 +725,14 @@ Triangles::remove_inside_vertex (db::Vertex *vertex, std::listcreate_triangle (outer_edges[0], outer_edges[1], outer_edges[2]); triangles_to_fix.insert (nt); } for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { triangles_to_fix.erase (*t); - remove_triangle (*t); + mp_graph->remove_polygon (*t); } if (new_triangles_out) { @@ -870,23 +741,23 @@ Triangles::remove_inside_vertex (db::Vertex *vertex, std::list to_fix_a (triangles_to_fix.begin (), triangles_to_fix.end ()); - fix_triangles (to_fix_a, std::vector (), new_triangles_out); + std::vector to_fix_a (triangles_to_fix.begin (), triangles_to_fix.end ()); + fix_triangles (to_fix_a, std::vector (), new_triangles_out); } void -Triangles::fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles) +Triangulation::fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles) { m_level += 1; for (auto e = fixed_edges.begin (); e != fixed_edges.end (); ++e) { (*e)->set_level (m_level); } - std::set queue, todo; + std::set queue, todo; for (auto t = tris.begin (); t != tris.end (); ++t) { for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = (*t)->edge (i); + Edge *e = (*t)->edge (i); if (e->level () < m_level && ! e->is_segment ()) { queue.insert (e); } @@ -923,14 +794,14 @@ Triangles::fix_triangles (const std::vector &tris, const std::ve tl_assert (! is_illegal_edge (s12)); // TODO: remove later! for (int i = 0; i < 3; ++i) { - db::TriangleEdge *s1 = t1->edge (i); + Edge *s1 = t1->edge (i); if (s1->level () < m_level && ! s1->is_segment ()) { queue.insert (s1); } } for (int i = 0; i < 3; ++i) { - db::TriangleEdge *s2 = t2->edge (i); + Edge *s2 = t2->edge (i); if (s2->level () < m_level && ! s2->is_segment ()) { queue.insert (s2); } @@ -944,10 +815,10 @@ Triangles::fix_triangles (const std::vector &tris, const std::ve } bool -Triangles::is_illegal_edge (db::TriangleEdge *edge) +Triangulation::is_illegal_edge (Edge *edge) { - db::Triangle *left = edge->left (); - db::Triangle *right = edge->right (); + Polygon *left = edge->left (); + Polygon *right = edge->right (); if (!left || !right) { return false; } @@ -967,11 +838,11 @@ Triangles::is_illegal_edge (db::TriangleEdge *edge) return false; } -std::pair, TriangleEdge *> -Triangles::flip (TriangleEdge *edge) +std::pair, Edge *> +Triangulation::flip (Edge *edge) { - db::Triangle *t1 = edge->left (); - db::Triangle *t2 = edge->right (); + Polygon *t1 = edge->left (); + Polygon *t2 = edge->right (); bool outside = t1->is_outside (); tl_assert (t1->is_outside () == outside); @@ -980,43 +851,43 @@ Triangles::flip (TriangleEdge *edge) t1->unlink (); t2->unlink (); - db::Vertex *t1_vext = t1->opposite (edge); - db::TriangleEdge *t1_sext1 = t1->find_edge_with (t1_vext, edge->v1 ()); - db::TriangleEdge *t1_sext2 = t1->find_edge_with (t1_vext, edge->v2 ()); + Vertex *t1_vext = t1->opposite (edge); + Edge *t1_sext1 = t1->find_edge_with (t1_vext, edge->v1 ()); + Edge *t1_sext2 = t1->find_edge_with (t1_vext, edge->v2 ()); - db::Vertex *t2_vext = t2->opposite (edge); - db::TriangleEdge *t2_sext1 = t2->find_edge_with (t2_vext, edge->v1 ()); - db::TriangleEdge *t2_sext2 = t2->find_edge_with (t2_vext, edge->v2 ()); + Vertex *t2_vext = t2->opposite (edge); + Edge *t2_sext1 = t2->find_edge_with (t2_vext, edge->v1 ()); + Edge *t2_sext2 = t2->find_edge_with (t2_vext, edge->v2 ()); - db::TriangleEdge *s_new = create_edge (t1_vext, t2_vext); + Edge *s_new = mp_graph->create_edge (t1_vext, t2_vext); - db::Triangle *t1_new = create_triangle (s_new, t1_sext1, t2_sext1); + Polygon *t1_new = mp_graph->create_triangle (s_new, t1_sext1, t2_sext1); t1_new->set_outside (outside); - db::Triangle *t2_new = create_triangle (s_new, t1_sext2, t2_sext2); + Polygon *t2_new = mp_graph->create_triangle (s_new, t1_sext2, t2_sext2); t2_new->set_outside (outside); - remove_triangle (t1); - remove_triangle (t2); + mp_graph->remove_polygon (t1); + mp_graph->remove_polygon (t2); return std::make_pair (std::make_pair (t1_new, t2_new), s_new); } -std::vector -Triangles::fill_concave_corners (const std::vector &edges) +std::vector +Triangulation::fill_concave_corners (const std::vector &edges) { - std::vector res; - std::vector points, terminals; + std::vector res; + std::vector points, terminals; - std::map > vertex2edge; + std::map > vertex2edge; for (auto e = edges.begin (); e != edges.end (); ++e) { - auto i = vertex2edge.insert (std::make_pair ((*e)->v1 (), std::vector ())); + auto i = vertex2edge.insert (std::make_pair ((*e)->v1 (), std::vector ())); if (i.second) { points.push_back ((*e)->v1 ()); } i.first->second.push_back (*e); - i = vertex2edge.insert (std::make_pair ((*e)->v2 (), std::vector ())); + i = vertex2edge.insert (std::make_pair ((*e)->v2 (), std::vector ())); if (i.second) { points.push_back ((*e)->v2 ()); } @@ -1033,17 +904,17 @@ Triangles::fill_concave_corners (const std::vector &edges) } } tl_assert (terminals.size () == size_t (2)); - db::Vertex *v = terminals[0]; + Vertex *v = terminals[0]; bool any_connected = false; - db::Vertex *vp = 0; + Vertex *vp = 0; - std::set to_remove; + std::set to_remove; while (vertex2edge [v].size () >= size_t (2) || ! vp) { - db::TriangleEdge *seg = 0; - std::vector &ee = vertex2edge [v]; + Edge *seg = 0; + std::vector &ee = vertex2edge [v]; for (auto e = ee.begin (); e != ee.end (); ++e) { if (! (*e)->has_vertex (vp)) { seg = (*e); @@ -1052,16 +923,16 @@ Triangles::fill_concave_corners (const std::vector &edges) } tl_assert (seg != 0); - db::Triangle *tri = seg->left () ? seg->left () : seg->right (); - db::Vertex *vn = seg->other (v); + Polygon *tri = seg->left () ? seg->left () : seg->right (); + Vertex *vn = seg->other (v); - std::vector &een = vertex2edge [vn]; + std::vector &een = vertex2edge [vn]; if (een.size () < size_t (2)) { break; } tl_assert (een.size () == size_t (2)); - db::TriangleEdge *segn = 0; + Edge *segn = 0; for (auto e = een.begin (); e != een.end (); ++e) { if (! (*e)->has_vertex (v)) { segn = (*e); @@ -1070,15 +941,15 @@ Triangles::fill_concave_corners (const std::vector &edges) } tl_assert (segn != 0); - db::Vertex *vnn = segn->other (vn); - std::vector &eenn = vertex2edge [vnn]; + Vertex *vnn = segn->other (vn); + std::vector &eenn = vertex2edge [vnn]; // NOTE: tri can be None in case a lonely edge stays after removing // attached triangles if (! tri || seg->side_of (*vnn) * seg->side_of (*tri->opposite (seg)) < 0) { // is concave - db::TriangleEdge *new_edge = create_edge (v, vnn); + Edge *new_edge = mp_graph->create_edge (v, vnn); for (auto e = ee.begin (); e != ee.end (); ++e) { if (*e == seg) { ee.erase (e); @@ -1098,7 +969,7 @@ Triangles::fill_concave_corners (const std::vector &edges) vertex2edge.erase (vn); to_remove.insert (vn); - db::Triangle *new_triangle = create_triangle (seg, segn, new_edge); + Polygon *new_triangle = mp_graph->create_triangle (seg, segn, new_edge); res.push_back (new_triangle); any_connected = true; @@ -1115,7 +986,7 @@ Triangles::fill_concave_corners (const std::vector &edges) break; } - std::vector::iterator wp = points.begin (); + std::vector::iterator wp = points.begin (); for (auto p = points.begin (); p != points.end (); ++p) { if (to_remove.find (*p) == to_remove.end ()) { *wp++ = *p; @@ -1128,25 +999,25 @@ Triangles::fill_concave_corners (const std::vector &edges) return res; } -std::vector -Triangles::search_edges_crossing (Vertex *from, Vertex *to) +std::vector +Triangulation::search_edges_crossing (Vertex *from, Vertex *to) { - db::Vertex *v = from; - db::Vertex *vv = to; + Vertex *v = from; + Vertex *vv = to; db::DEdge edge (*from, *to); - db::Triangle *current_triangle = 0; - db::TriangleEdge *next_edge = 0; + Polygon *current_triangle = 0; + Edge *next_edge = 0; - std::vector result; + std::vector result; for (auto e = v->begin_edges (); e != v->end_edges () && ! next_edge; ++e) { - for (auto t = (*e)->begin_triangles (); t != (*e)->end_triangles (); ++t) { - db::TriangleEdge *os = t->opposite (v); + for (auto t = (*e)->begin_polygons (); t != (*e)->end_polygons (); ++t) { + Edge *os = t->opposite (v); if (os->has_vertex (vv)) { return result; } - if (os->crosses (edge)) { + if (os->crosses_including (edge)) { result.push_back (os); current_triangle = t.operator-> (); next_edge = os; @@ -1164,15 +1035,15 @@ Triangles::search_edges_crossing (Vertex *from, Vertex *to) // Note that we're convex, so there has to be a path across triangles tl_assert (current_triangle != 0); - db::TriangleEdge *cs = next_edge; + Edge *cs = next_edge; next_edge = 0; for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = current_triangle->edge (i); + Edge *e = current_triangle->edge (i); if (e != cs) { if (e->has_vertex (vv)) { return result; } - if (e->crosses (edge)) { + if (e->crosses_including (edge)) { result.push_back (e); next_edge = e; break; @@ -1185,14 +1056,14 @@ Triangles::search_edges_crossing (Vertex *from, Vertex *to) } } -db::Vertex * -Triangles::find_vertex_for_point (const db::DPoint &point) +Vertex * +Triangulation::find_vertex_for_point (const db::DPoint &point) const { - db::TriangleEdge *edge = find_closest_edge (point); + Edge *edge = find_closest_edge (point); if (!edge) { return 0; } - db::Vertex *v = 0; + Vertex *v = 0; if (is_equal (*edge->v1 (), point)) { v = edge->v1 (); } else if (is_equal (*edge->v2 (), point)) { @@ -1201,10 +1072,10 @@ Triangles::find_vertex_for_point (const db::DPoint &point) return v; } -db::TriangleEdge * -Triangles::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) +Edge * +Triangulation::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) const { - db::Vertex *v = find_vertex_for_point (p1); + Vertex *v = find_vertex_for_point (p1); if (!v) { return 0; } @@ -1216,24 +1087,65 @@ Triangles::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) return 0; } -std::vector -Triangles::ensure_edge_inner (db::Vertex *from, db::Vertex *to) +std::vector +Triangulation::find_vertexes_along_line (const db::DPoint &p1, const db::DPoint &p2) const +{ + db::DEdge e12 (p1, p2); + + Vertex *v = find_vertex_for_point (p1); + if (!v) { + v = find_vertex_for_point (p2); + e12.swap_points (); + } + + std::vector result; + result.push_back (v); + + while (v) { + Vertex *vn = 0; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + Vertex *vv = (*e)->other (v); + int cs = 0; + if (db::vprod_sign (e12.d (), *vv - *v) == 0 && db::sprod_sign (e12.d (), *vv - *v) > 0 && (cs = db::sprod_sign (e12.d (), *vv - e12.p2 ())) <= 0) { + result.push_back (vv); + if (cs < 0) { + // continue searching + vn = vv; + } + break; + } + } + v = vn; + } + + return result; +} + +static bool is_touching (const db::DEdge &a, const db::DEdge &b) +{ + return (a.side_of (b.p1 ()) == 0 || a.side_of (b.p2 ()) == 0); +} + +std::vector +Triangulation::ensure_edge_inner (Vertex *from, Vertex *to) { auto crossed_edges = search_edges_crossing (from, to); - std::vector result; + std::vector result; + + db::DEdge dedge (*from , *to); if (crossed_edges.empty ()) { // no crossing edge - there should be a edge already - db::TriangleEdge *res = find_edge_for_points (*from, *to); + Edge *res = find_edge_for_points (*from, *to); tl_assert (res != 0); result.push_back (res); - } else if (crossed_edges.size () == 1) { + } else if (crossed_edges.size () == 1 && ! is_touching (dedge, crossed_edges.front ()->edge ())) { // can be solved by flipping auto pp = flip (crossed_edges.front ()); - db::TriangleEdge *res = pp.second; + Edge *res = pp.second; tl_assert (res->has_vertex (from) && res->has_vertex (to)); result.push_back (res); @@ -1241,18 +1153,27 @@ Triangles::ensure_edge_inner (db::Vertex *from, db::Vertex *to) // split edge close to center db::DPoint split_point; + Edge *split_edge = 0; double d = -1.0; double l_half = 0.25 * (*to - *from).sq_length (); for (auto e = crossed_edges.begin (); e != crossed_edges.end (); ++e) { - db::DPoint p = (*e)->intersection_point (db::DEdge (*from, *to)); + db::DPoint p = (*e)->intersection_point (dedge); double dp = fabs ((p - *from).sq_length () - l_half); if (d < 0.0 || dp < d) { dp = d; split_point = p; + split_edge = *e; } } - db::Vertex *split_vertex = insert_point (split_point); + Vertex *split_vertex; + if (dedge.side_of (*split_edge->v1 ()) == 0) { + split_vertex = split_edge->v1 (); + } else if (dedge.side_of (*split_edge->v2 ()) == 0) { + split_vertex = split_edge->v2 (); + } else { + split_vertex = insert_point (split_point); + } result = ensure_edge_inner (from, split_vertex); @@ -1264,8 +1185,8 @@ Triangles::ensure_edge_inner (db::Vertex *from, db::Vertex *to) return result; } -std::vector -Triangles::ensure_edge (db::Vertex *from, db::Vertex *to) +std::vector +Triangulation::ensure_edge (Vertex *from, Vertex *to) { #if 0 // NOTE: this should not be required if the original segments are non-overlapping @@ -1284,48 +1205,53 @@ Triangles::ensure_edge (db::Vertex *from, db::Vertex *to) } void -Triangles::join_edges (std::vector &edges) +Triangulation::join_edges (std::vector &edges) { // edges are supposed to be ordered for (size_t i = 1; i < edges.size (); ) { - db::TriangleEdge *s1 = edges [i - 1]; - db::TriangleEdge *s2 = edges [i]; + Edge *s1 = edges [i - 1]; + Edge *s2 = edges [i]; tl_assert (s1->is_segment () == s2->is_segment ()); - db::Vertex *cp = s1->common_vertex (s2); + Vertex *cp = s1->common_vertex (s2); tl_assert (cp != 0); - std::vector join_edges; - for (auto e = cp->begin_edges (); e != cp->end_edges (); ++e) { - if (*e != s1 && *e != s2) { - if ((*e)->can_join_via (cp)) { - join_edges.push_back (*e); - } else { - join_edges.clear (); - break; + std::vector join_edges; + + if (! cp->is_precious ()) { + + for (auto e = cp->begin_edges (); e != cp->end_edges (); ++e) { + if (*e != s1 && *e != s2) { + if ((*e)->can_join_via (cp)) { + join_edges.push_back (*e); + } else { + join_edges.clear (); + break; + } } } + } if (! join_edges.empty ()) { tl_assert (join_edges.size () <= 2); - TriangleEdge *new_edge = create_edge (s1->other (cp), s2->other (cp)); + Edge *new_edge = mp_graph->create_edge (s1->other (cp), s2->other (cp)); new_edge->set_is_segment (s1->is_segment ()); for (auto js = join_edges.begin (); js != join_edges.end (); ++js) { - db::Triangle *t1 = (*js)->left (); - db::Triangle *t2 = (*js)->right (); - db::TriangleEdge *tedge1 = t1->opposite (cp); - db::TriangleEdge *tedge2 = t2->opposite (cp); + Polygon *t1 = (*js)->left (); + Polygon *t2 = (*js)->right (); + Edge *tedge1 = t1->opposite (cp); + Edge *tedge2 = t2->opposite (cp); t1->unlink (); t2->unlink (); - db::Triangle *tri = create_triangle (tedge1, tedge2, new_edge); + Polygon *tri = mp_graph->create_triangle (tedge1, tedge2, new_edge); tri->set_outside (t1->is_outside ()); - remove_triangle (t1); - remove_triangle (t2); + mp_graph->remove_polygon (t1); + mp_graph->remove_polygon (t2); } edges [i - 1] = new_edge; @@ -1339,11 +1265,11 @@ Triangles::join_edges (std::vector &edges) } void -Triangles::constrain (const std::vector > &contours) +Triangulation::constrain (const std::vector > &contours) { tl_assert (! m_is_constrained); - std::vector > > resolved_edges; + std::vector > > resolved_edges; for (auto c = contours.begin (); c != contours.end (); ++c) { for (auto v = c->begin (); v != c->end (); ++v) { @@ -1353,26 +1279,26 @@ Triangles::constrain (const std::vector > &contours) vv = c->begin (); } db::DEdge e (**v, **vv); - resolved_edges.push_back (std::make_pair (e, std::vector ())); + resolved_edges.push_back (std::make_pair (e, std::vector ())); resolved_edges.back ().second = ensure_edge (*v, *vv); } } - for (auto tri = mp_triangles.begin (); tri != mp_triangles.end (); ++tri) { + for (auto tri = mp_graph->polygons ().begin (); tri != mp_graph->polygons ().end (); ++tri) { tri->set_outside (false); for (int i = 0; i < 3; ++i) { tri->edge (i)->set_is_segment (false); } } - std::set new_tri; + std::set new_tri; for (auto re = resolved_edges.begin (); re != resolved_edges.end (); ++re) { auto edge = re->first; auto edges = re->second; for (auto e = edges.begin (); e != edges.end (); ++e) { (*e)->set_is_segment (true); - db::Triangle *outer_tri = 0; + Polygon *outer_tri = 0; int d = db::sprod_sign (edge.d (), (*e)->d ()); if (d > 0) { outer_tri = (*e)->left (); @@ -1389,7 +1315,7 @@ Triangles::constrain (const std::vector > &contours) while (! new_tri.empty ()) { - std::set next_tris; + std::set next_tris; for (auto tri = new_tri.begin (); tri != new_tri.end (); ++tri) { for (int i = 0; i < 3; ++i) { @@ -1418,58 +1344,48 @@ Triangles::constrain (const std::vector > &contours) } void -Triangles::remove_outside_triangles () +Triangulation::remove_outside_triangles () { tl_assert (m_is_constrained); // NOTE: don't remove while iterating - std::vector to_remove; - for (auto tri = begin (); tri != end (); ++tri) { + std::vector to_remove; + for (auto tri = mp_graph->begin (); tri != mp_graph->end (); ++tri) { if (tri->is_outside ()) { - to_remove.push_back (const_cast (tri.operator-> ())); + to_remove.push_back (const_cast (tri.operator-> ())); } } for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { - remove_triangle (*t); + mp_graph->remove_polygon (*t); } } -void -Triangles::clear () -{ - mp_triangles.clear (); - m_edges_heap.clear (); - m_vertex_heap.clear (); - m_returned_edges.clear (); - m_is_constrained = false; - m_level = 0; - m_id = 0; -} template void -Triangles::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) +Triangulation::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) { - edge_contours.push_back (std::vector ()); + edge_contours.push_back (std::vector ()); for (auto pt = poly.begin_hull (); pt != poly.end_hull (); ++pt) { edge_contours.back ().push_back (insert_point (trans * *pt)); } for (unsigned int h = 0; h < poly.holes (); ++h) { - edge_contours.push_back (std::vector ()); + edge_contours.push_back (std::vector ()); for (auto pt = poly.begin_hole (h); pt != poly.end_hole (h); ++pt) { edge_contours.back ().push_back (insert_point (trans * *pt)); } } } +template DB_PUBLIC void Triangulation::make_contours (const db::Polygon &, const db::CplxTrans &, std::vector > &); +template DB_PUBLIC void Triangulation::make_contours (const db::DPolygon &, const db::DCplxTrans &, std::vector > &); + void -Triangles::create_constrained_delaunay (const db::Region ®ion, const CplxTrans &trans) +Triangulation::create_constrained_delaunay (const db::Region ®ion, const CplxTrans &trans) { - clear (); - - std::vector > edge_contours; + std::vector > edge_contours; for (auto p = region.begin_merged (); ! p.at_end (); ++p) { make_contours (*p, trans, edge_contours); @@ -1479,36 +1395,24 @@ Triangles::create_constrained_delaunay (const db::Region ®ion, const CplxTran } void -Triangles::create_constrained_delaunay (const db::Polygon &p, const std::vector &vertexes, const CplxTrans &trans) +Triangulation::create_constrained_delaunay (const db::Polygon &p, const CplxTrans &trans) { - clear (); - - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true); - } - - std::vector > edge_contours; + std::vector > edge_contours; make_contours (p, trans, edge_contours); constrain (edge_contours); } void -Triangles::create_constrained_delaunay (const db::DPolygon &p, const std::vector &vertexes, const DCplxTrans &trans) +Triangulation::create_constrained_delaunay (const db::DPolygon &p, const DCplxTrans &trans) { - clear (); - - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true); - } - - std::vector > edge_contours; + std::vector > edge_contours; make_contours (p, trans, edge_contours); constrain (edge_contours); } -static bool is_skinny (const db::Triangle *tri, const Triangles::TriangulateParameters ¶m) +static bool is_skinny (const Polygon *tri, const TriangulationParameters ¶m) { if (param.min_b < db::epsilon) { return false; @@ -1519,7 +1423,7 @@ static bool is_skinny (const db::Triangle *tri, const Triangles::TriangulatePara } } -static bool is_invalid (const db::Triangle *tri, const Triangles::TriangulateParameters ¶m) +static bool is_invalid (const Polygon *tri, const TriangulationParameters ¶m) { if (is_skinny (tri, param)) { return true; @@ -1542,82 +1446,141 @@ static bool is_invalid (const db::Triangle *tri, const Triangles::TriangulatePar } void -Triangles::triangulate (const db::Region ®ion, const TriangulateParameters ¶meters, double dbu) +Triangulation::triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, double dbu) { tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + clear (); + create_constrained_delaunay (region, db::CplxTrans (dbu)); refine (parameters); } void -Triangles::triangulate (const db::Region ®ion, const TriangulateParameters ¶meters, const db::CplxTrans &trans) +Triangulation::triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, const db::CplxTrans &trans) { tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + clear (); + create_constrained_delaunay (region, trans); refine (parameters); } void -Triangles::triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, double dbu) +Triangulation::triangulate (const db::Region ®ion, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans) +{ + tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + + clear (); + + std::vector > edge_contours; + for (auto p = region.begin_merged (); ! p.at_end (); ++p) { + make_contours (*p, trans, edge_contours); + } + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + + constrain (edge_contours); + refine (parameters); +} + +void +Triangulation::triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, double dbu) { triangulate (poly, std::vector (), parameters, dbu); } void -Triangles::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, double dbu) +Triangulation::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, double dbu) { tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - create_constrained_delaunay (poly, vertexes, db::CplxTrans (dbu)); + db::CplxTrans trans (dbu); + + clear (); + + std::vector > edge_contours; + make_contours (poly, trans, edge_contours); + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + + constrain (edge_contours); refine (parameters); } void -Triangles::triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, const db::CplxTrans &trans) +Triangulation::triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, const db::CplxTrans &trans) { triangulate (poly, std::vector (), parameters, trans); } void -Triangles::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const db::CplxTrans &trans) +Triangulation::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans) { tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - create_constrained_delaunay (poly, vertexes, trans); + clear (); + + std::vector > edge_contours; + make_contours (poly, trans, edge_contours); + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + + constrain (edge_contours); refine (parameters); } void -Triangles::triangulate (const db::DPolygon &poly, const TriangulateParameters ¶meters, const DCplxTrans &trans) +Triangulation::triangulate (const db::DPolygon &poly, const TriangulationParameters ¶meters, const DCplxTrans &trans) { triangulate (poly, std::vector (), parameters, trans); } void -Triangles::triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const DCplxTrans &trans) +Triangulation::triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, const DCplxTrans &trans) { tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - create_constrained_delaunay (poly, vertexes, trans); + clear (); + + std::vector > edge_contours; + make_contours (poly, trans, edge_contours); + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + + constrain (edge_contours); refine (parameters); } void -Triangles::refine (const TriangulateParameters ¶meters) +Triangulation::refine (const TriangulationParameters ¶meters) { if (parameters.min_b < db::epsilon && parameters.max_area < db::epsilon && parameters.max_area_border < db::epsilon) { // no refinement requested - we're done. - remove_outside_triangles (); + if (parameters.remove_outside_triangles) { + remove_outside_triangles (); + } return; } unsigned int nloop = 0; - std::list > new_triangles; - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { + std::list > new_triangles; + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { new_triangles.push_back (t.operator-> ()); } @@ -1629,7 +1592,7 @@ Triangles::refine (const TriangulateParameters ¶meters) tl::info << "Iteration " << nloop << " .."; } - std::list > to_consider; + std::list > to_consider; for (auto t = new_triangles.begin (); t != new_triangles.end (); ++t) { if (t->get () && ! (*t)->is_outside () && is_invalid (t->get (), parameters)) { to_consider.push_back (*t); @@ -1686,16 +1649,16 @@ Triangles::refine (const TriangulateParameters ¶meters) } else { - db::Vertex *vstart = 0; + Vertex *vstart = 0; for (unsigned int i = 0; i < 3; ++i) { - db::TriangleEdge *edge = (*t)->edge (i); + Edge *edge = (*t)->edge (i); vstart = (*t)->opposite (edge); if (edge->side_of (*vstart) * edge->side_of (center) < 0) { break; } } - db::TriangleEdge *edge = find_closest_edge (center, vstart, true /*inside only*/); + Edge *edge = find_closest_edge (center, vstart, true /*inside only*/); tl_assert (edge != 0); if (! edge->is_segment () || edge->side_of (*vstart) * edge->side_of (center) >= 0) { @@ -1715,10 +1678,10 @@ Triangles::refine (const TriangulateParameters ¶meters) if (tl::verbosity () >= parameters.base_verbosity + 20) { tl::info << "Split edge " << edge->to_string (true) << " at " << pnew.to_string (); } - db::Vertex *vnew = insert_point (pnew, &new_triangles); + Vertex *vnew = insert_point (pnew, &new_triangles); auto vertexes_in_diametral_circle = find_points_around (vnew, sr); - std::vector to_delete; + std::vector to_delete; for (auto v = vertexes_in_diametral_circle.begin (); v != vertexes_in_diametral_circle.end (); ++v) { bool has_segment = false; for (auto e = (*v)->begin_edges (); e != (*v)->end_edges () && ! has_segment; ++e) { @@ -1752,7 +1715,7 @@ Triangles::refine (const TriangulateParameters ¶meters) if (parameters.mark_triangles) { - for (auto t = begin (); t != end (); ++t) { + for (auto t = mp_graph->begin (); t != mp_graph->end (); ++t) { size_t id = 0; @@ -1772,13 +1735,17 @@ Triangles::refine (const TriangulateParameters ¶meters) } - (const_cast (t.operator->()))->set_id (id); + (const_cast (t.operator->()))->set_id (id); } } - remove_outside_triangles (); + if (parameters.remove_outside_triangles) { + remove_outside_triangles (); + } } -} +} // namespace plc + +} // namespace db diff --git a/src/db/db/dbPLCTriangulation.h b/src/db/db/dbPLCTriangulation.h new file mode 100644 index 000000000..3d3f30d4d --- /dev/null +++ b/src/db/db/dbPLCTriangulation.h @@ -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 +#include +#include +#include + +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::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 ®ion, const TriangulationParameters ¶meters, double dbu = 1.0); + + // more versions + void triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void triangulate (const db::Region ®ion, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, double dbu = 1.0); + void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, double dbu = 1.0); + void triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Triangulates a floating-point polygon + */ + void triangulate (const db::DPolygon &poly, const TriangulationParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); + void triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, 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 > *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 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 ®ion, 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 ¶m); + + /** + * @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 > &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 void make_contours (const Poly &poly, const Trans &trans, std::vector > &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 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 > *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 > *new_triangles = 0); + + /** + * @brief Flips the given edge + */ + std::pair, 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 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 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 find_touching (const db::DBox &box) const; + std::vector find_inside_circle (const db::DPoint ¢er, 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 > *new_triangles = 0); + void remove_inside_vertex (Vertex *vertex, std::list > *new_triangles_out = 0); + std::vector fill_concave_corners (const std::vector &edges); + void fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles); + std::vector 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 > *new_triangles = 0); + void split_triangle (Polygon *t, Vertex *vertex, std::list > *new_triangles_out); + void split_triangles_on_edge (Vertex *vertex, Edge *split_edge, std::list > *new_triangles_out); + void add_more_triangles (std::vector &new_triangles, + Edge *incoming_edge, + Vertex *from_vertex, Vertex *to_vertex, + Edge *conn_edge); + void insert_new_vertex(Vertex *vertex, std::list > *new_triangles_out); + std::vector ensure_edge_inner (Vertex *from, Vertex *to); + void join_edges (std::vector &edges); +}; + +} // namespace plc + +} // namespace db + +#endif + diff --git a/src/db/db/dbPropertiesRepository.cc b/src/db/db/dbPropertiesRepository.cc index 790b2a919..bba686627 100644 --- a/src/db/db/dbPropertiesRepository.cc +++ b/src/db/db/dbPropertiesRepository.cc @@ -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); } } diff --git a/src/db/db/dbPropertiesRepository.h b/src/db/db/dbPropertiesRepository.h index a5bbf7a9f..99388e326 100644 --- a/src/db/db/dbPropertiesRepository.h +++ b/src/db/db/dbPropertiesRepository.h @@ -358,7 +358,7 @@ class DB_PUBLIC PropertiesRepository * 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 diff --git a/src/db/db/dbRegionProcessors.cc b/src/db/db/dbRegionProcessors.cc index 3c3449107..730f6ea7a 100644 --- a/src/db/db/dbRegionProcessors.cc +++ b/src/db/db/dbRegionProcessors.cc @@ -453,8 +453,8 @@ TriangulationProcessor::process (const db::Polygon &poly, std::vector - -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 -Vertex::triangles () const -{ - std::set seen; - std::vector 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 ¢er, 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 -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; -} - -} diff --git a/src/db/db/dbTriangle.h b/src/db/db/dbTriangle.h deleted file mode 100644 index 332d1c695..000000000 --- a/src/db/db/dbTriangle.h +++ /dev/null @@ -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 -#include -#include -#include - -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 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 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 ¢er, 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 ¢er, 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, 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 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 - diff --git a/src/db/db/dbTriangles.h b/src/db/db/dbTriangles.h deleted file mode 100644 index 31da3f48b..000000000 --- a/src/db/db/dbTriangles.h +++ /dev/null @@ -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 -#include -#include -#include - -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::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 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 ®ion, const TriangulateParameters ¶meters, double dbu = 1.0); - - // more versions - void triangulate (const db::Region ®ion, const TriangulateParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); - void triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, double dbu = 1.0); - void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, double dbu = 1.0); - void triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); - void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); - - /** - * @brief Triangulates a floating-point polygon - */ - void triangulate (const db::DPolygon &poly, const TriangulateParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); - void triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, 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 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 > *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 > *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 > *new_triangles = 0); - - /** - * @brief Flips the given edge - */ - std::pair, 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 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 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 > &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 ®ion, 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 &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 &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 find_touching (const db::DBox &box) const; - std::vector find_inside_circle (const db::DPoint ¢er, 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 mp_triangles; - tl::stable_vector m_edges_heap; - std::vector m_returned_edges; - tl::stable_vector 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 > *new_triangles = 0); - void remove_inside_vertex (db::Vertex *vertex, std::list > *new_triangles_out = 0); - std::vector fill_concave_corners (const std::vector &edges); - void fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles); - std::vector 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 > *new_triangles = 0); - void split_triangle (db::Triangle *t, db::Vertex *vertex, std::list > *new_triangles_out); - void split_triangles_on_edge (db::Vertex *vertex, db::TriangleEdge *split_edge, std::list > *new_triangles_out); - void add_more_triangles (std::vector &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 > *new_triangles_out); - std::vector ensure_edge_inner (db::Vertex *from, db::Vertex *to); - void join_edges (std::vector &edges); - void refine (const TriangulateParameters ¶m); - template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); -}; - -} - -#endif - diff --git a/src/db/db/gsiDeclDbLayoutToNetlist.cc b/src/db/db/gsiDeclDbLayoutToNetlist.cc index c3a565c50..065677906 100644 --- a/src/db/db/gsiDeclDbLayoutToNetlist.cc +++ b/src/db/db/gsiDeclDbLayoutToNetlist.cc @@ -1033,7 +1033,7 @@ Class 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" diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index 8282198ab..a5cb6f994 100644 --- a/src/db/db/gsiDeclDbPolygon.cc +++ b/src/db/db/gsiDeclDbPolygon.cc @@ -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 -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 -static std::vector

polygons_from_triangles (const db::Triangles &tri, const T &trans) +static std::vector

polygons_from_graph (const db::plc::Graph &plc, const T &trans) { std::vector

result; - result.reserve (tri.num_triangles ()); - - typename P::point_type pts [3]; + result.reserve (plc.num_polygons ()); - 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 to_polygon (const db::polygon &p) } template -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::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 ())); + + decomp.decompose (to_polygon (*p), param, trans); + + return region_from_graph (plc, trans.inverted ()); +} + +template +static std::vector

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 (plc, trans.inverted ()); +} + +template +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), param, trans); + triangulation.triangulate (to_polygon (*p), param, trans); - return region_from_triangles (tris, trans.inverted ()); + return region_from_graph (tris, trans.inverted ()); } template -static db::Region triangulate_ipolygon_v (const P *p, const std::vector &vertexes, double max_area = 0.0, double min_b = 0.0, double dbu = 0.001) +static db::Region triangulate_ipolygon_v (const P *p, const std::vector &vertexes, double max_area, double min_b, double dbu) { - 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 * 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), vertexes, param, trans); - return region_from_triangles (tris, trans.inverted ()); + return region_from_graph (tris, trans.inverted ()); } template static std::vector

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 (tris, trans.inverted ()); + return polygons_from_graph (tris, trans.inverted ()); } template static std::vector

triangulate_dpolygon_v (const P *p, const std::vector &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 (tris, trans.inverted ()); + return polygons_from_graph (tris, trans.inverted ()); } template @@ -880,36 +952,35 @@ Class decl_SimplePolygon ("db", "SimplePolygon", "\n" "This method has been introduced in version 0.18.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_ipolygon, + 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, 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, 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::methods (), "@brief A simple polygon class\n" @@ -1006,24 +1077,20 @@ Class decl_DSimplePolygon ("db", "DSimplePolygon", "\n" "This method has been introduced in version 0.25.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_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, 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, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), @@ -2207,36 +2274,36 @@ Class decl_Polygon ("db", "Polygon", "\n" "This method was introduced in version 0.18.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_ipolygon, + 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, 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, 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::methods (), "@brief A polygon class\n" @@ -2360,24 +2427,20 @@ Class decl_DPolygon ("db", "DPolygon", "\n" "This method has been introduced in version 0.25.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_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, 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, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), diff --git a/src/db/db/gsiDeclDbTrans.cc b/src/db/db/gsiDeclDbTrans.cc index f9c579986..27938b0d4 100644 --- a/src/db/db/gsiDeclDbTrans.cc +++ b/src/db/db/gsiDeclDbTrans.cc @@ -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" diff --git a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc new file mode 100644 index 000000000..007e6d4d6 --- /dev/null +++ b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc @@ -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 +#include +#include +#include + +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 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 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 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 ly (plc.to_layout ()); + db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au5.gds"); +} + diff --git a/src/db/unit_tests/dbPLCGraphTests.cc b/src/db/unit_tests/dbPLCGraphTests.cc new file mode 100644 index 000000000..1e91948f3 --- /dev/null +++ b/src/db/unit_tests/dbPLCGraphTests.cc @@ -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 +#include + +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); +} diff --git a/src/db/unit_tests/dbTriangleTests.cc b/src/db/unit_tests/dbPLCTests.cc similarity index 55% rename from src/db/unit_tests/dbTriangleTests.cc rename to src/db/unit_tests/dbPLCTests.cc index 4d0eb9e78..3c7745484 100644 --- a/src/db/unit_tests/dbTriangleTests.cc +++ b/src/db/unit_tests/dbPLCTests.cc @@ -21,30 +21,65 @@ */ -#include "dbTriangle.h" +#include "dbPLC.h" #include "tlUnitTest.h" #include #include -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; + + 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) + { } - TestableTriangleEdge (db::Vertex *v1, db::Vertex *v2) - : db::TriangleEdge (v1, v2) + 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 e1 (new TestableTriangleEdge (&v1, &v2)); + std::unique_ptr 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 e2 (new TestableTriangleEdge (&v2, &v3)); + std::unique_ptr 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 e1 (new TestableTriangleEdge (&v1, &v2)); + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); e1->link (); - std::unique_ptr e2 (new TestableTriangleEdge (&v2, &v3)); + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); e2->link (); - std::unique_ptr e3 (new TestableTriangleEdge (&v3, &v1)); + std::unique_ptr e3 (new TestableEdge (&v3, &v1)); e3->link (); - std::unique_ptr tri (new db::Triangle (e1.get (), e2.get (), e3.get ())); + std::unique_ptr 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 e4 (new TestableTriangleEdge (&v1, &v4)); + std::unique_ptr e4 (new TestableEdge (&v1, &v4)); e4->link (); - std::unique_ptr e5 (new TestableTriangleEdge (&v2, &v4)); + std::unique_ptr e5 (new TestableEdge (&v2, &v4)); e5->link (); - std::unique_ptr tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ())); + std::unique_ptr 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 e1 (new TestableTriangleEdge (&v1, &v2)); - std::unique_ptr e2 (new TestableTriangleEdge (&v2, &v3)); - std::unique_ptr e3 (new TestableTriangleEdge (&v3, &v1)); + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); + std::unique_ptr e3 (new TestableEdge (&v3, &v1)); - std::unique_ptr tri (new db::Triangle (e1.get (), e2.get (), e3.get ())); + std::unique_ptr tri (new TestablePolygon (e1.get (), e2.get (), e3.get ())); - std::unique_ptr e4 (new TestableTriangleEdge (&v1, &v4)); - std::unique_ptr e5 (new TestableTriangleEdge (&v2, &v4)); - std::unique_ptr tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ())); + std::unique_ptr e4 (new TestableEdge (&v1, &v4)); + std::unique_ptr e5 (new TestableEdge (&v2, &v4)); + std::unique_ptr 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 m_heap; + std::list 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); - - 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); + 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 (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); diff --git a/src/db/unit_tests/dbTrianglesTests.cc b/src/db/unit_tests/dbPLCTriangulationTests.cc similarity index 63% rename from src/db/unit_tests/dbTrianglesTests.cc rename to src/db/unit_tests/dbPLCTriangulationTests.cc index df7c5e357..2e3845e54 100644 --- a/src/db/unit_tests/dbTrianglesTests.cc +++ b/src/db/unit_tests/dbPLCTriangulationTests.cc @@ -21,7 +21,7 @@ */ -#include "dbTriangles.h" +#include "dbPLCTriangulation.h" #include "dbWriter.h" #include "dbRegionProcessors.h" #include "tlUnitTest.h" @@ -33,56 +33,73 @@ #include #include -class TestableTriangles - : public db::Triangles +namespace +{ + +class TestableTriangulation + : public db::plc::Triangulation +{ +public: + 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::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::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 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 (*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 vset; - std::vector vertexes; - for (auto t = tris.begin (); t != tris.end (); ++t) { + std::set vset; + std::vector 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 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 contour; + std::vector contour; for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { contour.push_back (tris.insert_point (ee[i].p1 ())); } - std::vector > contours; + std::vector > 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 contour; + std::vector contour; for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { contour.push_back (tris.insert_point (ee[i].p1 ())); } - std::vector > contours; + std::vector > 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 vertexes; + std::vector 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 sp1 (p1.begin (), p1.end ()); - std::set sp2 (p2.begin (), p2.end ()); + std::set sp1 (p1.begin (), p1.end ()); + std::set 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 ®ion, double dbu) +static void read_polygons (const std::string &path, db::Region ®ion, 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 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 (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 (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.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); } diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index ecb3c7ccb..ace029ba3 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -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 \ diff --git a/src/gsi/gsi_test/gsiTest.cc b/src/gsi/gsi_test/gsiTest.cc index 29363acca..27ac72de1 100644 --- a/src/gsi/gsi_test/gsiTest.cc +++ b/src/gsi/gsi_test/gsiTest.cc @@ -218,7 +218,7 @@ A::ia_cref_to_qs (const std::vector &ia) { QString s; for (std::vector::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 &ia) static QString s; s.clear (); for (std::vector::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 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 decl_a_nc (decl_a, "", "A_NC"); diff --git a/src/gsi/gsi_test/gsiTest.h b/src/gsi/gsi_test/gsiTest.h index c2b4bf58b..100d8dd2d 100644 --- a/src/gsi/gsi_test/gsiTest.h +++ b/src/gsi/gsi_test/gsiTest.h @@ -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 ft_cv (const std::vector &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 m_d; int n; diff --git a/src/klayout.pri b/src/klayout.pri index e7f90e037..067942aec 100644 --- a/src/klayout.pri +++ b/src/klayout.pri @@ -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 diff --git a/src/klayout.pro b/src/klayout.pro index 2bb0bf555..a7dde2251 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -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") { diff --git a/src/klayout_main/klayout_main/klayout.cc b/src/klayout_main/klayout_main/klayout.cc index db01f3c52..5f0ebaac8 100644 --- a/src/klayout_main/klayout_main/klayout.cc +++ b/src/klayout_main/klayout_main/klayout.cc @@ -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" diff --git a/src/pex/pex.pro b/src/pex/pex.pro new file mode 100644 index 000000000..9bcdb2590 --- /dev/null +++ b/src/pex/pex.pro @@ -0,0 +1,6 @@ + +TEMPLATE = subdirs +SUBDIRS = pex unit_tests + +unit_tests.depends += pex + diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc new file mode 100644 index 000000000..a92fa42ec --- /dev/null +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -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 &vertex_ports, const std::vector &polygon_ports) +{ + std::unique_ptr p_network (new pex::RNetwork ()); + rex->extract (poly, vertex_ports, polygon_ports, *p_network); + return p_network.release (); +} + +Class 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 (), "[]"), gsi::arg ("polygon_ports", std::vector (), "[]"), + "@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" +); + +} + diff --git a/src/pex/pex/gsiDeclRNetExtractor.cc b/src/pex/pex/gsiDeclRNetExtractor.cc new file mode 100644 index 000000000..1b9b02f03 --- /dev/null +++ b/src/pex/pex/gsiDeclRNetExtractor.cc @@ -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 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 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 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 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::const_iterator tech_begin_vias (const pex::RExtractorTech *tech) +{ + return tech->vias.begin (); +} + +static std::list::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::const_iterator tech_begin_conductors (const pex::RExtractorTech *tech) +{ + return tech->conductors.begin (); +} + +static std::list::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 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 *geo, + const std::map > *vertex_ports, + const std::map > *polygon_ports) +{ + std::unique_ptr network (new pex::RNetwork ()); + std::map empty_geo; + std::map > empty_vertex_ports; + std::map > 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 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" +); + +} + diff --git a/src/pex/pex/gsiDeclRNetwork.cc b/src/pex/pex/gsiDeclRNetwork.cc new file mode 100644 index 000000000..fb1c2a4f7 --- /dev/null +++ b/src/pex/pex/gsiDeclRNetwork.cc @@ -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 (mp_ptr); + } + +private: + tl::weak_ptr 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 (mp_ptr); + } + +private: + tl::weak_ptr 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_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 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 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 inject_NodeType_in_RNode (decl_NodeType.defs ()); + +Class 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 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" +); + +} + diff --git a/src/pex/pex/pex.pro b/src/pex/pex/pex.pro new file mode 100644 index 000000000..ab52eaf29 --- /dev/null +++ b/src/pex/pex/pex.pro @@ -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 + diff --git a/src/pex/pex/pexCommon.h b/src/pex/pex/pexCommon.h new file mode 100644 index 000000000..f978461ea --- /dev/null +++ b/src/pex/pex/pexCommon.h @@ -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 diff --git a/src/pex/pex/pexForceLink.cc b/src/pex/pex/pexForceLink.cc new file mode 100644 index 000000000..55c333593 --- /dev/null +++ b/src/pex/pex/pexForceLink.cc @@ -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; + } +} + diff --git a/src/pex/pex/pexForceLink.h b/src/pex/pex/pexForceLink.h new file mode 100644 index 000000000..69452fb3b --- /dev/null +++ b/src/pex/pex/pexForceLink.h @@ -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 + diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc new file mode 100644 index 000000000..10afe62cc --- /dev/null +++ b/src/pex/pex/pexRExtractor.cc @@ -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 .. +} + +} diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h new file mode 100644 index 000000000..e80c3f248 --- /dev/null +++ b/src/pex/pex/pexRExtractor.h @@ -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 &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) = 0; +}; + +} + +#endif + diff --git a/src/pex/pex/pexRExtractorTech.cc b/src/pex/pex/pexRExtractorTech.cc new file mode 100644 index 000000000..075f8417b --- /dev/null +++ b/src/pex/pex/pexRExtractorTech.cc @@ -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; +} + +} diff --git a/src/pex/pex/pexRExtractorTech.h b/src/pex/pex/pexRExtractorTech.h new file mode 100644 index 000000000..60bc6edda --- /dev/null +++ b/src/pex/pex/pexRExtractorTech.h @@ -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 +#include + +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 vias; + + /** + * @brief A list of conductor definitions + */ + std::list conductors; + + /** + * @brief A flag indicating to skip the simplify step after extraction + */ + bool skip_simplify; +}; + +} + +#endif + diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc new file mode 100644 index 000000000..977524dcc --- /dev/null +++ b/src/pex/pex/pexRNetExtractor.cc @@ -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 &geo, + const std::map > &vertex_ports, + const std::map > &polygon_ports, + RNetwork &rnetwork) +{ + rnetwork.clear (); + + std::map > 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 empty_vertex_ports; + const std::vector &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 empty_polygon_ports; + const std::vector &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 empty_via_ports; + const std::vector &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 > *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 > &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 &geo, + std::map > &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::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 children; + children.push_back (new db::CompoundRegionOperationPrimaryNode ()); + children.push_back (new db::CompoundRegionOperationSecondaryNode (const_cast (&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 +{ +public: + ExtractingReceiver (const RExtractorTechConductor *cond, + const std::vector *vertex_ports, + const std::vector *polygon_ports, + const std::vector *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 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 > m_interacting_ports; + const RExtractorTechConductor *mp_cond; + const std::vector *mp_vertex_ports; + const std::vector *mp_polygon_ports; + const std::vector *mp_via_ports; + std::map 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 &port_ids) + { + std::vector local_vertex_ports; + std::vector local_vertex_port_ids; + std::vector local_polygon_ports; + std::vector 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 &local_vertex_port_ids, + const std::vector &local_polygon_port_ids) + { + // create or find the new nodes in the target network + std::unordered_map 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 ®ion, + const std::vector &vertex_ports, + const std::vector &polygon_ports, + const std::vector &via_ports, + RNetwork &rnetwork) +{ + db::box_scanner2 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 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::box_convert ()); +} + +} diff --git a/src/pex/pex/pexRNetExtractor.h b/src/pex/pex/pexRNetExtractor.h new file mode 100644 index 000000000..befd52384 --- /dev/null +++ b/src/pex/pex/pexRNetExtractor.h @@ -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 &geo, + const std::map > &vertex_ports, + const std::map > &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 &geo, + std::map > &vias, + RNetwork &rnetwork); + + void extract_conductor (const RExtractorTechConductor &cond, + const db::Region ®ion, + const std::vector &vertex_ports, + const std::vector &polygon_ports, + const std::vector &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 > &vias, + RNetwork &rnetwork); +}; + +} + +#endif diff --git a/src/pex/pex/pexRNetwork.cc b/src/pex/pex/pexRNetwork.cc new file mode 100644 index 000000000..f64c60360 --- /dev/null +++ b/src/pex/pex/pexRNetwork.cc @@ -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 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 (node->m_elements.front ()); + } + delete node; +} + +void +RNetwork::remove_element (RElement *element) +{ + RNode *a = const_cast (element->a ()); + RNode *b = const_cast (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 ((*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 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 ((*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 ((*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 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 ((*e)->other (n.operator-> ())); + double r1 = (*e)->resistance (); + + ++e; + RNode *n2 = const_cast ((*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); + } + + } + +} + +} diff --git a/src/pex/pex/pexRNetwork.h b/src/pex/pex/pexRNetwork.h new file mode 100644 index 000000000..8de7dcb49 --- /dev/null +++ b/src/pex/pex/pexRNetwork.h @@ -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 +#include +#include + +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 +{ +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 &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 (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 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 +{ +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::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 (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::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 node_list; + typedef node_list::const_iterator node_iterator; + typedef tl::list 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, RElement *> m_elements_by_nodes; + std::map >, RNode *> m_nodes_by_type; + + RNetwork (const RNetwork &); + RNetwork &operator= (const RNetwork &); + + void join_nodes (RNode *a, RNode *b); +}; + +} + +#endif + diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc new file mode 100644 index 000000000..08b324d13 --- /dev/null +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -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 +{ +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 &interactions (size_t index) const + { + static std::set empty; + auto i = m_interactions.find (index); + if (i == m_interactions.end ()) { + return empty; + } else { + return i->second; + } + } + +private: + std::map > m_interactions; +}; + +struct JoinEdgeSets +{ + void operator() (std::set &a, const std::set &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 &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 > &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 > edges; + for (auto e = db_poly.begin_edge (); ! e.at_end (); ++e) { + db::Edge et = trans * *e; + if (et.x1 () != et.x2 ()) { + std::set 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 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 &vertex_ports, const std::vector &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 scanner; + + std::vector > 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 bc; + scanner.process (interactions, 1, bc, bc); + + // Generate the internal ports: those are defined by edges connecting two polygons + + std::vector internal_port_edges; + std::map internal_ports; + std::vector > internal_port_indexes; + + for (auto i = decomp_polygons.begin (); i != decomp_polygons.end (); ++i) { + + internal_port_indexes.push_back (std::vector ()); + 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 > ports; + std::map 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 &pp_indexes = interactions.interactions (p - decomp_polygons.begin ()); + const std::vector &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 (); + } +} + +} + + diff --git a/src/pex/pex/pexSquareCountingRExtractor.h b/src/pex/pex/pexSquareCountingRExtractor.h new file mode 100644 index 000000000..e5228ad87 --- /dev/null +++ b/src/pex/pex/pexSquareCountingRExtractor.h @@ -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 &vertex_ports, const std::vector &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 > &ports, pex::RNetwork &rnetwork); + +private: + db::plc::ConvexDecompositionParameters m_decomp_param; + double m_dbu; + bool m_skip_simplify; +}; + +} + +#endif + diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc new file mode 100644 index 000000000..500a5c179 --- /dev/null +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -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 &vertex_ports, const std::vector &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 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 > 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 vertex2node; + std::unordered_set vports_present; + std::map 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 (*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 &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 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 (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 ((*e)->other (node)); + pex::RNode *n2 = const_cast ((*ee)->other (node)); + double c = (*e)->conductance * (*ee)->conductance / s_sum; + rnetwork.create_element (c, n1, n2); + } + } + } + + rnetwork.remove_node (node); +} + +} diff --git a/src/pex/pex/pexTriangulationRExtractor.h b/src/pex/pex/pexTriangulationRExtractor.h new file mode 100644 index 000000000..634cf81eb --- /dev/null +++ b/src/pex/pex/pexTriangulationRExtractor.h @@ -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 &vertex_ports, const std::vector &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 &vertex2node, RNetwork &rnetwork); + void eliminate_node (pex::RNode *node, RNetwork &rnetwork); + void eliminate_all (RNetwork &rnetwork); +}; + +} + +#endif + diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc new file mode 100644 index 000000000..35ab0169a --- /dev/null +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -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" + ); +} diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc new file mode 100644 index 000000000..b8fd19f71 --- /dev/null +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -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 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 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 > 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 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 > 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 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 > vertex_ports; + std::map > 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" + ); +} diff --git a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc new file mode 100644 index 000000000..5b566152d --- /dev/null +++ b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc @@ -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 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 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 > 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 vertex_ports; + vertex_ports.push_back (db::Point (0, 50)); // V0 + vertex_ports.push_back (db::Point (1650, 50)); // V1 + + std::vector 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 vertex_ports; + vertex_ports.push_back (db::Point (300, 0)); // V0 + vertex_ports.push_back (db::Point (4300, 1000)); // V1 + + std::vector 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 :) + ) +} diff --git a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc new file mode 100644 index 000000000..ab485e7cb --- /dev/null +++ b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc @@ -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 vertex_ports; + vertex_ports.push_back (db::Point (0, 50)); // V0 + vertex_ports.push_back (db::Point (1000, 50)); // V1 + + std::vector 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 vertex_ports; + + std::vector 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 vertex_ports; + + std::vector 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 vertex_ports; + + std::vector 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 vertex_ports; + + std::vector 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 vertex_ports; + + std::vector 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 vertex_ports; + vertex_ports.push_back (db::Point (-50, 50)); + + std::vector 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 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 vertex_ports; + + std::vector 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 vertex_ports; + vertex_ports.push_back (db::Point (300, 0)); // V0 + vertex_ports.push_back (db::Point (4300, 1000)); // V1 + + std::vector 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? + ) +} + diff --git a/src/pex/unit_tests/unit_tests.pro b/src/pex/unit_tests/unit_tests.pro new file mode 100644 index 000000000..6ef8b5f93 --- /dev/null +++ b/src/pex/unit_tests/unit_tests.pro @@ -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 + diff --git a/src/pya/pya/pyaConvert.cc b/src/pya/pya/pyaConvert.cc index 89ecf851f..d7db77eee 100644 --- a/src/pya/pya/pyaConvert.cc +++ b/src/pya/pya/pyaConvert.cc @@ -502,7 +502,7 @@ PyObject *c2python_func::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 ()) { diff --git a/src/pymod/distutils_src/klayout/dbcore.pyi b/src/pymod/distutils_src/klayout/dbcore.pyi index 65fc5acd7..703445e19 100644 --- a/src/pymod/distutils_src/klayout/dbcore.pyi +++ b/src/pymod/distutils_src/klayout/dbcore.pyi @@ -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,32 +67638,32 @@ 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 (non-const version). - - - This constness variant has been introduced in version 0.26.8 + @brief Gets the circuit referenced by the subcircuit. """ ... @overload def circuit_ref(self) -> Circuit: r""" - @brief Gets the circuit referenced by the subcircuit. + @brief Gets the circuit referenced by the subcircuit (non-const version). + + + This constness variant has been introduced in version 0.26.8 """ ... @overload @@ -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""" diff --git a/src/pymod/distutils_src/klayout/pex/__init__.py b/src/pymod/distutils_src/klayout/pex/__init__.py new file mode 100644 index 000000000..883a7f0b1 --- /dev/null +++ b/src/pymod/distutils_src/klayout/pex/__init__.py @@ -0,0 +1,4 @@ + +import sys +from ..pexcore import __all__ +from ..pexcore import * diff --git a/src/pymod/distutils_src/klayout/pexcore.pyi b/src/pymod/distutils_src/klayout/pexcore.pyi new file mode 100644 index 000000000..a91d281cb --- /dev/null +++ b/src/pymod/distutils_src/klayout/pexcore.pyi @@ -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 diff --git a/src/pymod/pex/pex.pro b/src/pymod/pex/pex.pro new file mode 100644 index 000000000..d705cced3 --- /dev/null +++ b/src/pymod/pex/pex.pro @@ -0,0 +1,14 @@ + +TARGET = pexcore +REALMODULE = pex +PYI = pexcore.pyi + +include($$PWD/../pymod.pri) + +SOURCES = \ + pexMain.cc \ + +HEADERS += \ + +LIBS += -lklayout_pex + diff --git a/src/pymod/pex/pexMain.cc b/src/pymod/pex/pexMain.cc new file mode 100644 index 000000000..2f64943d2 --- /dev/null +++ b/src/pymod/pex/pexMain.cc @@ -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) diff --git a/src/pymod/pex/pexMain.h b/src/pymod/pex/pexMain.h new file mode 100644 index 000000000..0777186bf --- /dev/null +++ b/src/pymod/pex/pexMain.h @@ -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" diff --git a/src/pymod/pymod.pro b/src/pymod/pymod.pro index 1fac8d258..b286f9a74 100644 --- a/src/pymod/pymod.pro +++ b/src/pymod/pymod.pro @@ -4,6 +4,7 @@ include($$PWD/../klayout.pri) TEMPLATE = subdirs SUBDIRS = \ db \ + pex \ tl \ rdb \ lib \ diff --git a/src/pymod/unit_tests/pymod_tests.cc b/src/pymod/unit_tests/pymod_tests.cc index 15cca459a..8d78544b7 100644 --- a/src/pymod/unit_tests/pymod_tests.cc +++ b/src/pymod/unit_tests/pymod_tests.cc @@ -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") diff --git a/src/rba/rba/rbaConvert.cc b/src/rba/rba/rbaConvert.cc index 61817a5a7..cd8aa5d0d 100644 --- a/src/rba/rba/rbaConvert.cc +++ b/src/rba/rba/rbaConvert.cc @@ -29,6 +29,8 @@ #include "gsiDecl.h" +#include + namespace rba { @@ -120,9 +122,17 @@ tl::Variant ruby2c (VALUE rval) } } else if (TYPE (rval) == T_STRING) { - return tl::Variant (ruby2c (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 (rval)); + } else { + return tl::Variant (ruby2c > (rval)); + } + } else { - return tl::Variant (ruby2c (rba_safe_obj_as_string (rval))); + return tl::Variant (ruby2c (rba_safe_obj_as_string (rval))); } } @@ -261,7 +271,7 @@ VALUE c2ruby (const tl::Variant &c) } else if (c.is_bool ()) { return c2ruby (c.to_bool ()); } else if (c.is_a_string ()) { - return c2ruby (c.to_string ()); + return c2ruby (c.to_stdstring ()); } else if (c.is_a_bytearray ()) { return c2ruby > (c.to_bytearray ()); } else if (c.is_long () || c.is_char ()) { diff --git a/src/rba/rba/rbaConvert.h b/src/rba/rba/rbaConvert.h index 01533f843..1f1e0ee14 100644 --- a/src/rba/rba/rbaConvert.h +++ b/src/rba/rba/rbaConvert.h @@ -461,7 +461,7 @@ inline VALUE c2ruby (const float &c) template <> inline VALUE c2ruby (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 (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 & 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))); } } diff --git a/src/rba/rba/rbaMarshal.cc b/src/rba/rba/rbaMarshal.cc index 0781f5e7d..ba5f3e3e7 100644 --- a/src/rba/rba/rbaMarshal.cc +++ b/src/rba/rba/rbaMarshal.cc @@ -679,7 +679,7 @@ struct reader 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 ())); } } }; diff --git a/src/rba/rba/rbaUtils.h b/src/rba/rba/rbaUtils.h index 99422bd6d..e4d4f1684 100644 --- a/src/rba/rba/rbaUtils.h +++ b/src/rba/rba/rbaUtils.h @@ -135,6 +135,20 @@ inline void rb_hash_clear(VALUE hash) #endif +#if HAVE_RUBY_VERSION_CODE < 20200 + +#include + +// 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); /** diff --git a/src/rba/unit_tests/rbaTests.cc b/src/rba/unit_tests/rbaTests.cc index 7b23e3940..e2ff18d3a 100644 --- a/src/rba/unit_tests/rbaTests.cc +++ b/src/rba/unit_tests/rbaTests.cc @@ -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") diff --git a/src/tl/tl/tlInternational.cc b/src/tl/tl/tlInternational.cc index 343a5ffa4..f9d4e60b8 100644 --- a/src/tl/tl/tlInternational.cc +++ b/src/tl/tl/tlInternational.cc @@ -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 diff --git a/src/unit_tests/unit_test_main.cc b/src/unit_tests/unit_test_main.cc index 20c8696dc..6391b619c 100644 --- a/src/unit_tests/unit_test_main.cc +++ b/src/unit_tests/unit_test_main.cc @@ -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" diff --git a/src/with_all_libs.pri b/src/with_all_libs.pri index c1c4812b9..e3576dd0f 100644 --- a/src/with_all_libs.pri +++ b/src/with_all_libs.pri @@ -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") { diff --git a/testdata/algo/hm_decomposition_au1.gds b/testdata/algo/hm_decomposition_au1.gds new file mode 100644 index 000000000..dd04b81d9 Binary files /dev/null and b/testdata/algo/hm_decomposition_au1.gds differ diff --git a/testdata/algo/hm_decomposition_au2.gds b/testdata/algo/hm_decomposition_au2.gds new file mode 100644 index 000000000..b6d27f373 Binary files /dev/null and b/testdata/algo/hm_decomposition_au2.gds differ diff --git a/testdata/algo/hm_decomposition_au3.gds b/testdata/algo/hm_decomposition_au3.gds new file mode 100644 index 000000000..bdfe51da0 Binary files /dev/null and b/testdata/algo/hm_decomposition_au3.gds differ diff --git a/testdata/algo/hm_decomposition_au4.gds b/testdata/algo/hm_decomposition_au4.gds new file mode 100644 index 000000000..3595c5d17 Binary files /dev/null and b/testdata/algo/hm_decomposition_au4.gds differ diff --git a/testdata/algo/hm_decomposition_au5.gds b/testdata/algo/hm_decomposition_au5.gds new file mode 100644 index 000000000..875e1be49 Binary files /dev/null and b/testdata/algo/hm_decomposition_au5.gds differ diff --git a/testdata/pex/netex_test1.gds b/testdata/pex/netex_test1.gds new file mode 100644 index 000000000..3eef63f00 Binary files /dev/null and b/testdata/pex/netex_test1.gds differ diff --git a/testdata/pex/netex_viagen1.gds b/testdata/pex/netex_viagen1.gds new file mode 100644 index 000000000..2d7195da2 Binary files /dev/null and b/testdata/pex/netex_viagen1.gds differ diff --git a/testdata/pex/netex_viagen2.gds b/testdata/pex/netex_viagen2.gds new file mode 100644 index 000000000..d218db493 Binary files /dev/null and b/testdata/pex/netex_viagen2.gds differ diff --git a/testdata/pymod/import_pex.py b/testdata/pymod/import_pex.py new file mode 100755 index 000000000..56f3c3bf4 --- /dev/null +++ b/testdata/pymod/import_pex.py @@ -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) diff --git a/testdata/python/basic.py b/testdata/python/basic.py index a0b39a025..9f604f9d0 100644 --- a/testdata/python/basic.py +++ b/testdata/python/basic.py @@ -2994,8 +2994,12 @@ def test_QByteArray(self): 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 @@ def test_QString(self): 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'") - - 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 ]) + 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, 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 @@ def test_QLatin1String(self): 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 @@ def test_binaryStrings(self): 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 ]) diff --git a/testdata/ruby/basic_testcore.rb b/testdata/ruby/basic_testcore.rb index 72a8d1e02..7db26da8b 100644 --- a/testdata/ruby/basic_testcore.rb +++ b/testdata/ruby/basic_testcore.rb @@ -2958,6 +2958,9 @@ def test_QByteArray 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 @@ def test_QString 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\"") - - 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 ]) - - 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([ 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, 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, 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 @@ def test_QLatin1String 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\"") - - 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 ]) - - 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([ 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, 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, 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 @@ def test_QStringView 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 @@ def test_QStringView 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 @@ def test_QStringView 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\"") - - 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 ]) + 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, 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\"") diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb index 3ac9c74e4..fc039c9a7 100644 --- a/testdata/ruby/dbPolygonTest.rb +++ b/testdata/ruby/dbPolygonTest.rb @@ -994,15 +994,41 @@ def test_triangulation 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 diff --git a/testdata/ruby/dbSimplePolygonTest.rb b/testdata/ruby/dbSimplePolygonTest.rb index 268d675ca..e598228ae 100644 --- a/testdata/ruby/dbSimplePolygonTest.rb +++ b/testdata/ruby/dbSimplePolygonTest.rb @@ -426,18 +426,43 @@ def test_triangulation 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") diff --git a/testdata/ruby/pexTests.rb b/testdata/ruby/pexTests.rb new file mode 100644 index 000000000..efb4e3706 --- /dev/null +++ b/testdata/ruby/pexTests.rb @@ -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") +