From e8e45b7272f6b50e0185afaf6894f7e653a53c41 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 Feb 2019 23:51:35 +0100 Subject: [PATCH] Some tests, smooth and round method of deep region --- src/db/db/dbDeepRegion.cc | 37 ++++++++++-- src/db/unit_tests/dbDeepRegionTests.cc | 76 +++++++++++++++++++++++++ testdata/algo/deep_region_au10.gds | Bin 0 -> 3306 bytes testdata/algo/deep_region_au11.gds | Bin 0 -> 16538 bytes 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 testdata/algo/deep_region_au10.gds create mode 100644 testdata/algo/deep_region_au11.gds diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index 89201171c..99b5ed41e 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -947,6 +947,7 @@ DeepRegion::holes () const const db::Shapes &s = c->shapes (m_merged_polygons.layer ()); db::Shapes &st = c->shapes (res->deep_layer ().layer ()); + db::PolygonRefToShapesGenerator pr (&layout, &st); for (db::Shapes::shape_iterator si = s.begin (db::ShapeIterator::All); ! si.at_end (); ++si) { for (size_t i = 0; i < si->holes (); ++i) { @@ -956,7 +957,7 @@ DeepRegion::holes () const pts.push_back (*p); } h.assign_hull (pts.begin (), pts.end ()); - st.insert (h); + pr.put (h); } } @@ -979,6 +980,7 @@ DeepRegion::hulls () const const db::Shapes &s = c->shapes (m_merged_polygons.layer ()); db::Shapes &st = c->shapes (res->deep_layer ().layer ()); + db::PolygonRefToShapesGenerator pr (&layout, &st); for (db::Shapes::shape_iterator si = s.begin (db::ShapeIterator::All); ! si.at_end (); ++si) { db::Polygon h; @@ -987,7 +989,7 @@ DeepRegion::hulls () const pts.push_back (*p); } h.assign_hull (pts.begin (), pts.end ()); - st.insert (h); + pr.put (h); } } @@ -1007,20 +1009,32 @@ DeepRegion::rounded_corners (double rinner, double router, unsigned int n) const { ensure_merged_polygons_valid (); - // @@@ scaled instances + db::MagnificationReducer red; + db::cell_variants_collector vars (red); + vars.collect (m_merged_polygons.layout (), m_merged_polygons.initial_cell ()); + + // NOTE: m_merged_polygons is mutable, so why is the const_cast needed? + const_cast (m_merged_polygons).separate_variants (vars); db::Layout &layout = m_merged_polygons.layout (); std::auto_ptr res (new db::DeepRegion (m_merged_polygons.new_layer ())); for (db::Layout::iterator c = layout.begin (); c != layout.end (); ++c) { + const std::map &v = vars.variants (c->cell_index ()); + tl_assert (v.size () == size_t (1)); + double mag = v.begin ()->first.mag (); + db::Coord rinner_with_mag = db::coord_traits::rounded (rinner / mag); + db::Coord router_with_mag = db::coord_traits::rounded (router / mag); + const db::Shapes &s = c->shapes (m_merged_polygons.layer ()); db::Shapes &st = c->shapes (res->deep_layer ().layer ()); + db::PolygonRefToShapesGenerator pr (&layout, &st); for (db::Shapes::shape_iterator si = s.begin (db::ShapeIterator::All); ! si.at_end (); ++si) { db::Polygon poly; si->polygon (poly); - st.insert (db::compute_rounded (poly, rinner, router, n)); + pr.put (db::compute_rounded (poly, rinner_with_mag, router_with_mag, n)); } } @@ -1033,20 +1047,31 @@ DeepRegion::smoothed (coord_type d) const { ensure_merged_polygons_valid (); - // @@@ scaled instances + db::MagnificationReducer red; + db::cell_variants_collector vars (red); + vars.collect (m_merged_polygons.layout (), m_merged_polygons.initial_cell ()); + + // NOTE: m_merged_polygons is mutable, so why is the const_cast needed? + const_cast (m_merged_polygons).separate_variants (vars); db::Layout &layout = m_merged_polygons.layout (); std::auto_ptr res (new db::DeepRegion (m_merged_polygons.new_layer ())); for (db::Layout::iterator c = layout.begin (); c != layout.end (); ++c) { + const std::map &v = vars.variants (c->cell_index ()); + tl_assert (v.size () == size_t (1)); + double mag = v.begin ()->first.mag (); + db::Coord d_with_mag = db::coord_traits::rounded (d / mag); + const db::Shapes &s = c->shapes (m_merged_polygons.layer ()); db::Shapes &st = c->shapes (res->deep_layer ().layer ()); + db::PolygonRefToShapesGenerator pr (&layout, &st); for (db::Shapes::shape_iterator si = s.begin (db::ShapeIterator::All); ! si.at_end (); ++si) { db::Polygon poly; si->polygon (poly); - st.insert (db::smooth (poly, d)); + pr.put (db::smooth (poly, d_with_mag)); } } diff --git a/src/db/unit_tests/dbDeepRegionTests.cc b/src/db/unit_tests/dbDeepRegionTests.cc index dcc86b417..8c7ddfb35 100644 --- a/src/db/unit_tests/dbDeepRegionTests.cc +++ b/src/db/unit_tests/dbDeepRegionTests.cc @@ -573,6 +573,82 @@ TEST(9_SizingWithBoolean) db::compare_layouts (_this, target, tl::testsrc () + "/testdata/algo/deep_region_au9e.gds"); } +TEST(10_HullsAndHoles) +{ + db::Layout ly; + { + std::string fn (tl::testsrc ()); + fn += "/testdata/algo/deep_region_area_peri_l1.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type top_cell_index = *ly.begin_top_down (); + db::Cell &top_cell = ly.cell (top_cell_index); + + db::DeepShapeStore dss; + dss.set_max_vertex_count (4); + dss.set_threads (0); + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + + db::Region r1 (db::RecursiveShapeIterator (ly, top_cell, l1), dss); + db::Region r1_sized = r1.sized (2000); + r1_sized -= r1; + + db::Region hulls = r1_sized.hulls (); + db::Region holes = r1_sized.holes (); + + db::Layout target; + unsigned int target_top_cell_index = target.add_cell (ly.cell_name (top_cell_index)); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (10, 0)), r1_sized); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (11, 0)), hulls); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (12, 0)), holes); + + CHECKPOINT(); + db::compare_layouts (_this, target, tl::testsrc () + "/testdata/algo/deep_region_au10.gds"); +} + +TEST(11_RoundAndSmoothed) +{ + db::Layout ly; + { + std::string fn (tl::testsrc ()); + fn += "/testdata/algo/deep_region_area_peri_l1.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type top_cell_index = *ly.begin_top_down (); + db::Cell &top_cell = ly.cell (top_cell_index); + + db::DeepShapeStore dss; + dss.set_max_vertex_count (4); + dss.set_threads (0); + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + + db::Region r1 (db::RecursiveShapeIterator (ly, top_cell, l1), dss); + db::Region r1_sized = r1.sized (2000); + r1_sized -= r1; + + db::Region rounded = r1_sized.rounded_corners (3000, 5000, 100); + db::Region smoothed = rounded.smoothed (100); + + db::Layout target; + unsigned int target_top_cell_index = target.add_cell (ly.cell_name (top_cell_index)); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (10, 0)), r1_sized); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (11, 0)), rounded); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (12, 0)), smoothed); + + CHECKPOINT(); + db::compare_layouts (_this, target, tl::testsrc () + "/testdata/algo/deep_region_au11.gds"); +} + TEST(100_Integration) { db::Layout ly; diff --git a/testdata/algo/deep_region_au10.gds b/testdata/algo/deep_region_au10.gds new file mode 100644 index 0000000000000000000000000000000000000000..b06543d136866e040083f991f6feec848d74ab15 GIT binary patch literal 3306 zcmcguJx>%-6umR-4hy0|FcO6X5=}6Kpi&?v#;|DPN08u0fC@Vs6Dj=z1{-6nj5b(F zNudc8mKqxq8xu+r8%ioeBhP!@eP?H8;Vqpco3q?E=YF31W+js}otJ?mZT~H)bV#3^ zlheiTQcJVDx28o-wNE@@f>hMeT{ABNPCF_7C+P#8rsLAYXA|hP_)#*=)`0GMdP}0*S%kpTix)q0N`*WBBkogjkx1E=I zQQ8g6mHlYeHtpJ{VlNAP<1z>Mdbn$S1k$b;<)hD-1MuN)`9pg=zQsVjhy_0VaDK4s z_ZIQUwOv;g9--A*mOZ#P ztIz2UWIdTOyxRNcMFyvA4&{@v!!9oJyYkvLhx~u!9oXF_ZgG#(WpVDzN?c(J zTh4ubA_Cl95_!CqBkhVsg_JgIVas<;`69O`!Ou}U>h0`o?ZDg5p2u_GsY;XTMvk<% z;`gq&{pHspz(1Nlra`1#F_wRl{B&-1QarxekfdYPIU?j8|H^!3D^T7=4f+`_wVnH?dxA(tL=~O3w0#nJQ81g zeyEdm6#GegQ~i8H+{LfgqW+jSvAoZ@&Z%qD?E!t#|0r(P;(j;p7xWR%i}jWGsEc)Q zuZgrb)yFrMug`SNy|Vl4w)TtnUg$gf+_*>WUTy3}F+ckE^#$GY-fHY&*h}t3l#h9- VUa2>)f7QK5+7&%t-;I@u`~%e!Sit}Q literal 0 HcmV?d00001 diff --git a/testdata/algo/deep_region_au11.gds b/testdata/algo/deep_region_au11.gds new file mode 100644 index 0000000000000000000000000000000000000000..978dd07addb3371f3699bb48c726cc051644f7c0 GIT binary patch literal 16538 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