From 9fad2ff1facfe166342055df8a65dc9a395b7aa8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Aug 2026 22:15:55 +0200 Subject: [PATCH] Fixing issue #2429 by introducing a rectangular envelope for triangulation Previously the algorithm tried to dynamically build a convex envelope. However, in case of the #2429 polygon, this rendered extremely skinny triangles which could not be removed. These caused numerical issues. --- src/db/db/dbPLC.cc | 8 +- src/db/db/dbPLC.h | 4 +- src/db/db/dbPLCTriangulation.cc | 47 ++++++---- src/db/unit_tests/dbPLCTriangulationTests.cc | 84 ++++++++++++++---- .../pexTriangulationRExtractorTests.cc | 2 +- testdata/algo/hm_decomposition_au1.gds | Bin 1636 -> 1636 bytes testdata/algo/hm_decomposition_au2.gds | Bin 1946 -> 1884 bytes testdata/algo/hm_decomposition_au3.gds | Bin 1492 -> 1540 bytes testdata/algo/hm_decomposition_au4.gds | Bin 1850 -> 1788 bytes testdata/algo/hm_decomposition_au5.gds | Bin 1322 -> 1322 bytes testdata/algo/hm_decomposition_au6.gds | Bin 2760 -> 2760 bytes testdata/algo/hm_decomposition_au7.gds | Bin 14920 -> 14824 bytes 12 files changed, 105 insertions(+), 40 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index 8d85580ba..28f70b706 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -903,10 +903,10 @@ Graph::bbox () const } db::Layout * -Graph::to_layout (bool decompose_by_id) const +Graph::to_layout (bool decompose_by_id, double dbu) const { db::Layout *layout = new db::Layout (); - layout->dbu (0.001); + layout->dbu (dbu); auto dbu_trans = db::CplxTrans (layout->dbu ()).inverted (); @@ -950,9 +950,9 @@ Graph::to_layout (bool decompose_by_id) const } void -Graph::dump (const std::string &path, bool decompose_by_id) const +Graph::dump (const std::string &path, bool decompose_by_id, double dbu) const { - std::unique_ptr ly (to_layout (decompose_by_id)); + std::unique_ptr ly (to_layout (decompose_by_id, dbu)); tl::OutputStream stream (path); diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index 6e098e81c..b1b479517 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -879,13 +879,13 @@ public: * 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; + void dump (const std::string &path, bool decompose_by_id = false, double dbu = 0.001) 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; + db::Layout *to_layout (bool decompose_by_id = false, double dbu = 0.001) const; protected: Vertex *create_vertex (double x, double y); diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index dff58d45b..63b77c981 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -42,9 +42,6 @@ static inline bool is_equal (const db::DPoint &a, const db::DPoint &b) std::abs (a.y () - b.y ()) < std::max (1.0, (std::abs (a.y ()) + std::abs (b.y ()))) * db::epsilon; } -// distance of point to vertex to be considered "on edge vertex" relative to edge length involved -const double snap_to_edge_vertex = 1e-5; - // distance of point to edge center to be considered "on edge center" relative to edge length involved const double snap_to_edge_center = 1e-3; @@ -252,7 +249,6 @@ Triangulation::insert (Vertex *vertex, std::list > *new_tr if (on_edge) { - // double snap_range = std::max (db::epsilon, snap_to_edge_vertex * e->length ()); @@@ double snap_range = std::max (db::epsilon, snap * on_edge->length ()); if (snap > 0.0 ? vertex->distance (*on_edge->v1 ()) < snap_range : is_equal (*vertex, *on_edge->v1 ())) { @@ -1142,17 +1138,23 @@ static bool is_touching (const db::DEdge &a, const db::DEdge &b) std::vector Triangulation::ensure_edge_inner (Vertex *from, Vertex *to) { - auto crossed_edges = search_edges_crossing (from, to); std::vector result; + // check if there is an edge already + Edge *already_there = find_edge_for_points (*from, *to); + if (already_there) { + result.push_back (already_there); + return result; + } + + auto crossed_edges = search_edges_crossing (from, to); + db::DEdge dedge (*from , *to); if (crossed_edges.empty ()) { // no crossing edge - there should be a edge already - Edge *res = find_edge_for_points (*from, *to); - tl_assert (res != 0); - result.push_back (res); + tl_assert (false); } else if (crossed_edges.size () == 1 && ! is_touching (dedge, crossed_edges.front ()->edge ())) { @@ -1173,7 +1175,7 @@ Triangulation::ensure_edge_inner (Vertex *from, Vertex *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; + d = dp; split_point = p; split_edge = *e; } @@ -1380,14 +1382,8 @@ void Triangulation::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) { edge_contours.push_back (std::vector ()); -// @@@int id = 0; // @@@ for (auto pt = poly.begin_hull (); pt != poly.end_hull (); ++pt) { -// @@@if (id == 27) { // @@@ -// @@@tl::info << "@@@ BANG!"; // @@@ -// @@@} // @@@ - // @@@ edge_contours.back ().push_back (insert_point (trans * *pt, 0, snap_to_edge_vertex)); edge_contours.back ().push_back (insert_point (trans * *pt)); -// @@@++id; mp_graph->dump ("xxx" + tl::to_string(id) + ".gds"); tl::info << "@@@ xxx" << id; // @@@ } for (unsigned int h = 0; h < poly.holes (); ++h) { @@ -1404,6 +1400,9 @@ template DB_PUBLIC void Triangulation::make_contours (const db::DPolygon &, cons void Triangulation::create_constrained_delaunay (const db::Region ®ion, const CplxTrans &trans) { + std::vector > box_contours; + make_contours (db::Polygon (region.bbox ()), trans, box_contours); + std::vector > edge_contours; for (auto p = region.begin_merged (); ! p.at_end (); ++p) { @@ -1416,6 +1415,9 @@ Triangulation::create_constrained_delaunay (const db::Region ®ion, const Cplx void Triangulation::create_constrained_delaunay (const db::Polygon &p, const CplxTrans &trans) { + std::vector > box_contours; + make_contours (db::Polygon (p.box ()), trans, box_contours); + std::vector > edge_contours; make_contours (p, trans, edge_contours); @@ -1425,6 +1427,9 @@ Triangulation::create_constrained_delaunay (const db::Polygon &p, const CplxTran void Triangulation::create_constrained_delaunay (const db::DPolygon &p, const DCplxTrans &trans) { + std::vector > box_contours; + make_contours (db::DPolygon (p.box ()), trans, box_contours); + std::vector > edge_contours; make_contours (p, trans, edge_contours); @@ -1493,6 +1498,9 @@ Triangulation::triangulate (const db::Region ®ion, const std::vector > box_contours; + make_contours (db::Polygon (region.bbox ()), trans, box_contours); + std::vector > edge_contours; for (auto p = region.begin_merged (); ! p.at_end (); ++p) { make_contours (*p, trans, edge_contours); @@ -1522,6 +1530,9 @@ Triangulation::triangulate (const db::Polygon &poly, const std::vector > box_contours; + make_contours (db::Polygon (poly.box ()), trans, box_contours); + std::vector > edge_contours; make_contours (poly, trans, edge_contours); @@ -1547,6 +1558,9 @@ Triangulation::triangulate (const db::Polygon &poly, const std::vector > box_contours; + make_contours (db::Polygon (poly.box ()), trans, box_contours); + std::vector > edge_contours; make_contours (poly, trans, edge_contours); @@ -1572,6 +1586,9 @@ Triangulation::triangulate (const db::DPolygon &poly, const std::vector > box_contours; + make_contours (db::DPolygon (poly.box ()), trans, box_contours); + std::vector > edge_contours; make_contours (poly, trans, edge_contours); diff --git a/src/db/unit_tests/dbPLCTriangulationTests.cc b/src/db/unit_tests/dbPLCTriangulationTests.cc index ac253a6a7..5a81b5ca1 100644 --- a/src/db/unit_tests/dbPLCTriangulationTests.cc +++ b/src/db/unit_tests/dbPLCTriangulationTests.cc @@ -896,8 +896,8 @@ TEST(triangulate_geo) } EXPECT_LT (n_skinny, size_t (20)); - EXPECT_GT (plc.num_polygons (), size_t (29000)); - EXPECT_LT (plc.num_polygons (), size_t (30000)); + EXPECT_GT (plc.num_polygons (), size_t (30000)); + EXPECT_LT (plc.num_polygons (), size_t (30200)); } TEST(triangulate_analytic) @@ -951,8 +951,8 @@ TEST(triangulate_analytic) EXPECT_GE (t->b (), param.min_b); } - EXPECT_GT (plc.num_polygons (), size_t (1250)); - EXPECT_LT (plc.num_polygons (), size_t (1300)); + EXPECT_GT (plc.num_polygons (), size_t (1300)); + EXPECT_LT (plc.num_polygons (), size_t (1340)); } TEST(triangulate_problematic) @@ -1116,26 +1116,74 @@ TEST(triangulate_issue_2429) double dbu = 1.0; - db::plc::TriangulationParameters param; - param.min_b = 0.3; - param.max_area = 500.0; // @@@ + { + db::plc::TriangulationParameters param; + param.min_b = 0.3; + param.max_area = 0.0; - 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); + 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); - EXPECT_EQ (tri.check (false), true); + EXPECT_EQ (tri.check (false), true); - // for debugging: - // tri.dump ("debug.gds"); + // for debugging: + // tri.dump ("debug.gds"); - for (auto t = plc.begin (); t != plc.end (); ++t) { - EXPECT_GE (t->b (), param.min_b); + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GE (plc.num_polygons (), size_t (65)); + EXPECT_LE (plc.num_polygons (), size_t (67)); } - EXPECT_GE (plc.num_polygons (), size_t (70)); - EXPECT_LE (plc.num_polygons (), size_t (72)); + { + db::plc::TriangulationParameters param; + param.min_b = 0.3; + param.max_area = 500.0; + + 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); + + EXPECT_EQ (tri.check (false), true); + + // for debugging: + // tri.dump ("debug.gds"); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GE (plc.num_polygons (), size_t (94)); + EXPECT_LE (plc.num_polygons (), size_t (96)); + } + + { + db::plc::TriangulationParameters param; + param.min_b = 0.9; + param.max_area = 50.0; + + 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); + + EXPECT_EQ (tri.check (false), true); + + // for debugging: + // tri.dump ("debug.gds"); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GE (plc.num_polygons (), size_t (670)); + EXPECT_LE (plc.num_polygons (), size_t (676)); + } } TEST(triangulate_with_vertexes) diff --git a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc index d441c0ee1..9d1689deb 100644 --- a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc +++ b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc @@ -357,7 +357,7 @@ TEST(extraction_meander) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R V0 V1 8.61417" // what is the "real" value? + "R V0 V1 8.60459" // what is the "real" value? ) } diff --git a/testdata/algo/hm_decomposition_au1.gds b/testdata/algo/hm_decomposition_au1.gds index dd04b81d925b3ab992d3ac34c8e92a3a3deff618..06bfe96bce771292da2a0f5c71cb183a37711afe 100644 GIT binary patch literal 1636 zcmb7^u};G<5QhIasT+`>K@`f+1&IkEbwNd4kU(7!MPdL6G52BM8CbgU3OoT%(1$>X zfq@aJ+}U>_GNg4fA_Czr3! zpQG{1VD@x>gMMoXXYKg*>Ka)N*bRdOcGCbjDpH^`qY*vMPJ!(Q`XE~j-EZ}0Tlk;% zv*b-&`sR6Of1R~_b$;?iK_-A&&O`jQd@)=szV|5a49p`&sUnXi775JB*})J1mk?HRQ&Kq=IuEOUY@L+o2)VEJIf>lCRnuZMp?A F`~VIcgh&7Y delta 349 zcmaFD^MoggfsKKQDS|{L4=vr&au&crL9pqI6m>C#Y*%-ip z`2~#BC5&Gf7?@v7PGHvI0;z;@&oCP>F{DgB z!E7-30t?6F92T=lEP8AZqbDz5F<|2`0a-FR2B@HpiGzuM0muZPS~D*G1rYmyLO}cY zgx^367487hlN(qKCx2$roqUhgj7$6qM5XW>u;vsNv&luQW*`@e0wtjSU=aj*fgOlh F7yy4pM`Hj0 diff --git a/testdata/algo/hm_decomposition_au2.gds b/testdata/algo/hm_decomposition_au2.gds index 279d6e3c44487a9b9a5775341f197837decceaf4..3c7853d9d28b9f0cad9b4590e657efbe0d4eacea 100644 GIT binary patch delta 285 zcmbQme}_+rfsKKQDS||Ifd1Xi^Bw@ih%*BDUVfWau18y>`u1SX3sjV#}KB#cnoPgk6M<9q0@e1_1jELtFp= delta 291 zcmZvWF-rqM6olWt-F>%gf{C7@g^h>lwg?iou)4G&Vk3x%m81yy2Z~r&1PkGu&O-77 z{0aFPsl>uMq)aXDMGy;LHO%*Bn2)uWRzX6{=d`Oho0Fq0TBi;*{_3je+$W?HHs3@8wwfprUGzS2h7y?76JFbJ6>JI%S7;qo>QeF0-;#7E*@y zuc_2m)Co;|g?b68s5(N**OrxfrU6y!Fd-H7qmc4NcikYQdh_2sy1F-_&Jy}c-S5@D qqT43)70nnS<*VUtu#k%8kd$)M!cDJ+RI>=pc~OJZd!tQLpui97@p!WU delta 304 zcmZqSxx%f)z{bGD6u}_F$i)7NfrCMmL6AX}fg71UQAt%8n{ZU5u!{gQ11dkT&CDe!;-N_=SOi`Nbq=9WE9h7;kb8 zvjG!B%H#}YgUJOf9Fuui%qA~j)?jLVBj!8q8TQ;F{)19%;-81W%2IhZ(1CI>L-z!bk@GT;O0WBkIv0I~wC*kJM`X1&QQtY%yY+a~j|>P*&Q zF`GPt#f(e9?En8i5)2FiW?*3jc2-uPef$eRCW9=v$C?Fp0?1V1H*hBuu$oOSVC7&E jzXDP?S%F<-G8c==WG}X?$*fss*8rIW2ly}uhy!aR1=vVg+p5{yF?;J3CHRTALw`Oux`A}-G|;eHq3>~#zbmDlnvq{hT2Ou th!SeNg}Y?OExyKZNQ`5j6TzpsL|6LQWs8x_U`28~iieX0sf70peF22yKP>w(PIh7RnEZ=T zX|ez-%j8+iMw6d0noZVXHk&NNWX2|bg@J+j-sCX$XLd}V9Wv*W@1@1If2<~vIwgf0AO$?&j0`b delta 184 zcmZ3*wTerLfsKKQDS|y<_5Sc8-a%A!uX0yq8EF6>n0Hp+2r2y5$ BDq{cu diff --git a/testdata/algo/hm_decomposition_au6.gds b/testdata/algo/hm_decomposition_au6.gds index 1c81106dcd6df06a52c94c0bc3bfbed5bd90d21c..88a0fac6564773cafcda97549853775b1e0c7c1c 100644 GIT binary patch delta 258 zcmX>hdO|dcfsKKQDS|{L4=vr&au%nZyO2 Q7t>p~PymhdO|dcfsKKQDS|{L4=vr&au z@3HDk=HcR)e1}zUa~Ycp<75Xmv(3BMH!x0Kz{xT>g-HOc0mx=zI59bo)nM{9&QAb8 CtUv|; diff --git a/testdata/algo/hm_decomposition_au7.gds b/testdata/algo/hm_decomposition_au7.gds index cae3d5cb19c454d231e00b4849f227b202c2f199..84854aa307107874c01767a05c60ce6c16b597ff 100644 GIT binary patch delta 1703 zcmZuxZD?C%6uwDwZ*G#_G->*oG-;MJS#$H9o7~*$DiOCCaluVt)S+cMklJ=7P{C~- zMFfA0>S%p%paorR#{@;|@~=u)=bu4m*(S)`hYZxA8yiI(ZC6=5_a>_j{PFTU=XuY2 z-gC}9?>+n4Z^J%@WAyqSrcI|e-eh>DiD_iI81BD(!WVG-pS-6HujmRB-}X!l?}SU2 zhu@ILtr`U*`U5EG&f-(98^sz4!|W`6<`hiVay#FEF__qcD3y0+SCh-)@s2dyi#D&CyKU-ewFzl`fbNO-Vc zId84=<9$aeoK|D37U(91Fh&?8bPzs6_#h)KLo8X{kn9R-8gh;u+PSLV$~yTU!ks#o zJ5RYs=w!8qjR=}2hU%Y1ks{32<*;mf!cvKHld>MeV^+Vr+KEaH|JJmsc}#rnM4~<^ zRIgaMKvx;@Q@yLP+Nh07H4lv1GT2v_N6AK?UZ7>qAzY{6N~;xLw#IR(Q36_eaI0aP z$9P+5*}a}p89|95ZRiJ`KJAtY^2F@C9$>XNramz6xsV)b66){YEUrHoQK1y zpwwJIXDdxpi=x|sEiD1I5~H^z1Ba^*mZm($XkBUTMjxf{w3DXB*@YW^AC_F#;dIAw zt&PVncZ}_)42Cf0P7341)ci`cBJjFHY=tem6Bu=y47VAE!%G>FRmnBH;7JNBu~Sq^ zGvQk}?+LLLcHfi4J04yaM?#EP`8aI{$Sut<>|GRP(Lh(6$Dlu9=8h0ur2RR>f+7|? z7G&F_;JjH(wrAk;$vEFGm$rCs=+NW)k)0#=Gx)46frOvuj7?-t!6u3L(BIFk{I2Cu z3@1%@iOrPDE9=?7i(2Q z!Xn{G*t{Z+1}%t-VcZKU=-WJ9dc5<7j=eyc25>GI!yZw=crc5yILKZmbvKs8G-kpQ zzDpUYS{k5}I4*|R3LA|i*~_H;fUDu8pv^87evW21i}64RzeX~sjmU6DW$W5@N}``u zGp&Pa5PT7l1`D8DxcVoc5vE@k_1Ej}gduy$ zS~1lJpnVEezM7S>yGw>5$;c~NIFt-Vl|gt(d%Npn*wQ9@<>edf3}c{}1_SdCt>EvJ delta 1742 zcmZuxZD>vcqfl%kvf;cy&unnzF5yoa}#q8wXcKTyKUY_&$ z+;h%7?|Wz0h21MSo--N?oX21^-{l0Zfve>Lob7ped__R~Kl4Tpe1`n^`2q$Ex$#}= zF=4y_**FaFy%=NNn5a@QW6ELCprUBVLFD(sX3k-T&*HbLDa^1unphUY6!)b`Mn6;W zms!PxWf0q|lUQrrhOm&ugtdS}<^qbs2Ar~GkhQ3IXrp-I>b9v6ttxievOx705Xe3t z2hg}YvbU(5luYhugkk8V)RC^U8(R-?K(fUf2w&es<(+oS?bS%{5U^w;;|Jz{EVk}=zob<=<1 zIP+B+Ymo3`!eNdR7~u#eWfx5D95y%af!~#Y8d`xt_neWR$EART5swWu{wQzWL79W- z^CawJB(0;PTc$lBc9k?94?JC_iVv^LA&2=s$MH8w(|t}Q#28slVxu?C3ymc1!vXTVJIkJuvNvctqNXj%@oD9KMXid zO6i54?ep}-A|;(BJcl7)i0RT{UmQLDo!HxMfww))KU$=qlZXdGxZf_IzdcrcfKmqO zrwkEN3ms?+*jS#Fh!*yFKww%3zK&MvG9ST~K-{`O#hZ(ib{9^+go8mFehkFyC&+r4 zmSGR!48}S`Oq2Ep<9HIXu#1&Ok(IVus9=5{-HIFgeG+;)RK$Y{c6S`Z_wDJTtMj&j z-JpmKaCF2FmsGq6Jb?K0zG zIKi}gNWKW9sEl=yG)_kp>+%{eG~#JW!p5i-=OQt_bby*TgG;wvjXMtqPI40$}1C4mN_JuA4JsMKvjn`*+rL>&908T$}%-lPbfoBjdQ zo*eWfHws)f`3RqN%lzBQCv^f^VE{b#Qh6dQ4sC`