From a9fa5d73f9dd7d25bc7006da0d6f98cc2e5e2d34 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Feb 2021 20:59:17 +0100 Subject: [PATCH] Introducing 'with_holes' and 'without_holes' in DRC and RBA::Region. --- src/db/db/dbRegionUtils.cc | 28 +++++++++++ src/db/db/dbRegionUtils.h | 45 ++++++++++++++++++ src/db/db/gsiDeclDbRegion.cc | 36 ++++++++++++++ src/db/unit_tests/dbRegionTests.cc | 21 +++++++++ src/drc/drc/built-in-macros/_drc_layer.rb | 57 ++++++++++++++++++++++- src/drc/unit_tests/drcSimpleTests.cc | 11 +++++ testdata/ruby/dbRegionTest.rb | 21 +++++++++ 7 files changed, 218 insertions(+), 1 deletion(-) diff --git a/src/db/db/dbRegionUtils.cc b/src/db/db/dbRegionUtils.cc index a1eacf9f7..0d2ed0de1 100644 --- a/src/db/db/dbRegionUtils.cc +++ b/src/db/db/dbRegionUtils.cc @@ -614,6 +614,34 @@ RectilinearFilter::vars () const return 0; } +// ------------------------------------------------------------------------------------- +// HoleCountFilter implementation + +HoleCountFilter::HoleCountFilter (size_t min_count, size_t max_count, bool inverse) + : m_min_count (min_count), m_max_count (max_count), m_inverse (inverse) +{ + // .. nothing yet .. +} + +bool +HoleCountFilter::selected (const db::Polygon &poly) const +{ + bool ok = poly.holes () < m_max_count && poly.holes () >= m_min_count; + return ok != m_inverse; +} + +bool +HoleCountFilter::selected (const db::PolygonRef &poly) const +{ + bool ok = poly.obj ().holes () < m_max_count && poly.obj ().holes () >= m_min_count; + return ok != m_inverse; +} + +const TransformationReducer *HoleCountFilter::vars () const +{ + return 0; +} + // ------------------------------------------------------------------------------------- // RectilinearFilter implementation diff --git a/src/db/db/dbRegionUtils.h b/src/db/db/dbRegionUtils.h index 28ce19f67..89392bd0d 100644 --- a/src/db/db/dbRegionUtils.h +++ b/src/db/db/dbRegionUtils.h @@ -286,6 +286,51 @@ private: bool m_inverse; }; +/** + * @brief Filters by number of holes + * + * This filter will select all polygons with a hole count between min_holes and max_holes (exclusively) + */ + +struct DB_PUBLIC HoleCountFilter + : public AllMustMatchFilter +{ + /** + * @brief Constructor + * @param inverse If set to true, only polygons not matching this criterion will be filtered + */ + HoleCountFilter (size_t min_count, size_t max_count, bool inverse); + + /** + * @brief Returns true if the polygon is a rectangle + */ + virtual bool selected (const db::Polygon &poly) const; + + /** + * @brief Returns true if the polygon is a rectangle + */ + virtual bool selected (const db::PolygonRef &poly) const; + + /** + * @brief This filter does not need variants + */ + virtual const TransformationReducer *vars () const; + + /** + * @brief This filter prefers producing variants + */ + virtual bool wants_variants () const { return true; } + + /** + * @brief This filter wants merged input + */ + virtual bool requires_raw_input () const { return false; } + +private: + size_t m_min_count, m_max_count; + bool m_inverse; +}; + /** * @brief A bounding box filter for use with Region::filter or Region::filtered * diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index a52ca5717..91ee1dfec 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -308,6 +308,18 @@ static db::Region with_area2 (const db::Region *r, const tl::Variant &min, const return r->filtered (f); } +static db::Region with_holes1 (const db::Region *r, size_t n, bool inverse) +{ + db::HoleCountFilter f (n, n + 1, inverse); + return r->filtered (f); +} + +static db::Region with_holes2 (const db::Region *r, const tl::Variant &min, const tl::Variant &max, bool inverse) +{ + db::HoleCountFilter f (min.is_nil () ? size_t (0) : min.to (), max.is_nil () ? std::numeric_limits ::max () : max.to (), inverse); + return r->filtered (f); +} + static db::Region with_bbox_width1 (const db::Region *r, db::Region::distance_type bbox_width, bool inverse) { db::RegionBBoxFilter f (bbox_width, bbox_width + 1, inverse, db::RegionBBoxFilter::BoxWidth); @@ -925,6 +937,30 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n" ) + + method_ext ("with_holes", with_holes1, gsi::arg ("nholes"), gsi::arg ("inverse"), + "@brief Filters the polygons by their number of holes\n" + "Filters the polygons of the region by number of holes. If \"inverse\" is false, only " + "polygons which have the given number of holes are returned. If \"inverse\" is true, " + "polygons not having the given of holes are returned.\n" + "\n" + "Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n" + "\n" + "This method has been introduced in version 0.27.\n" + ) + + method_ext ("with_holes", with_holes2, gsi::arg ("min_bholes"), gsi::arg ("max_nholes"), gsi::arg ("inverse"), + "@brief Filter the polygons by their number of holes\n" + "Filters the polygons of the region by number of holes. If \"inverse\" is false, only " + "polygons which have a hole count larger or equal to \"min_nholes\" and less than \"max_nholes\" are " + "returned. If \"inverse\" is true, " + "polygons having a hole count less than \"min_nholes\" or larger or equal than \"max_nholes\" are " + "returned.\n" + "\n" + "If you don't want to specify a lower or upper limit, pass nil to that parameter.\n" + "\n" + "Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n" + "\n" + "This method has been introduced in version 0.27.\n" + ) + method_ext ("with_bbox_width", with_bbox_width1, gsi::arg ("width"), gsi::arg ("inverse"), "@brief Filter the polygons by bounding box width\n" "Filters the polygons of the region by the width of their bounding box. If \"inverse\" is false, only " diff --git a/src/db/unit_tests/dbRegionTests.cc b/src/db/unit_tests/dbRegionTests.cc index db2d9d296..c8b2334fa 100644 --- a/src/db/unit_tests/dbRegionTests.cc +++ b/src/db/unit_tests/dbRegionTests.cc @@ -1973,6 +1973,27 @@ TEST(35c_interact_with_count_text) EXPECT_EQ (r.selected_not_interacting (rr, 3, 4).to_string (), "(0,0;0,200;100,200;100,0)"); } +TEST(40_with_holes) +{ + db::Region r; + r.insert (db::Box (db::Point (0, 0), db::Point (100, 200))); + db::Region rr; + rr.insert (db::Box (db::Point (10, 10), db::Point (20, 20))); + rr.insert (db::Box (db::Point (30, 30), db::Point (40, 40))); + r.set_merged_semantics (true); + r.set_min_coherence (false); + + r -= rr; + + EXPECT_EQ (rr.filtered (db::HoleCountFilter (0, 1, false)).to_string (), "(10,10;10,20;20,20;20,10);(30,30;30,40;40,40;40,30)"); + EXPECT_EQ (r.filtered (db::HoleCountFilter (2, 3, false)).to_string (), "(0,0;0,200;100,200;100,0/10,10;20,10;20,20;10,20/30,30;40,30;40,40;30,40)"); + EXPECT_EQ (r.filtered (db::HoleCountFilter (1, 2, false)).to_string (), ""); + EXPECT_EQ (r.filtered (db::HoleCountFilter (1, 3, false)).to_string (), "(0,0;0,200;100,200;100,0/10,10;20,10;20,20;10,20/30,30;40,30;40,40;30,40)"); + EXPECT_EQ (r.filtered (db::HoleCountFilter (0, 2, false)).to_string (), ""); + EXPECT_EQ (r.filtered (db::HoleCountFilter (2, 5, false)).to_string (), "(0,0;0,200;100,200;100,0/10,10;20,10;20,20;10,20/30,30;40,30;40,40;30,40)"); + EXPECT_EQ (r.filtered (db::HoleCountFilter (3, 5, true)).to_string (), "(0,0;0,200;100,200;100,0/10,10;20,10;20,20;10,20/30,30;40,30;40,40;30,40)"); +} + TEST(100_Processors) { db::Region r; diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index 0f1363f61..b5fa7a3df 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -488,7 +488,7 @@ CODE # # This method is available for polygon layers only. - %w(bbox_height bbox_max bbox_min bbox_width perimeter).each do |f| + %w(bbox_height bbox_max bbox_min bbox_width perimeter holes).each do |f| [true, false].each do |inv| mn = (inv ? "without" : "with") + "_" + f eval <<"CODE" @@ -517,6 +517,61 @@ CODE end end + # %DRC% + # @name with_holes + # @brief Selects all polygons with the specified number of holes + # @synopsis layer.with_holes(count) + # @synopsis layer.with_holes(min_count, max_count) + # @synopsis layer.with_holes(min_count .. max_count) + # + # This method is available for polygon layers. It will select all polygons from the input layer + # which have the specified number of holes. + + # %DRC% + # @name without_holes + # @brief Selects all polygons with the specified number of holes + # @synopsis layer.without_holes(count) + # @synopsis layer.without_holes(min_count, max_count) + # @synopsis layer.without_holes(min_count .. max_count) + # + # This method is available for polygon layers. It will select all polygons from the input layer + # which do not have the specified number of holes. + + %w(holes).each do |f| + [true, false].each do |inv| + mn = (inv ? "without" : "with") + "_" + f + eval <<"CODE" + def #{mn}(*args) + + @engine._context("#{mn}") do + + requires_region + if args.size == 1 + a = args[0] + if a.is_a?(Range) + min = @engine._make_numeric_value_with_nil(a.begin) + max = @engine._make_numeric_value_with_nil(a.end) + max && (max += 1) + DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, min, max, #{inv.inspect})) + else + DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_value(a), #{inv.inspect})) + end + elsif args.size == 2 + min = @engine._make_numeric_value_with_nil(args[0]) + max = @engine._make_numeric_value_with_nil(args[1]) + max && (max += 1) + DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, min, max, #{inv.inspect})) + else + raise("Invalid number of arguments (1 or 2 expected)") + end + + end + + end +CODE + end + end + # %DRC% # @name with_bbox_aspect_ratio # @brief Selects polygons by the aspect ratio of their bounding box diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index 9f0d45e6a..b3215743f 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -1152,3 +1152,14 @@ TEST(28_inputFragmentation) { run_test (_this, "28", true); } + +TEST(29_holes) +{ + run_test (_this, "29", false); +} + +TEST(29d_holes) +{ + run_test (_this, "29", true); +} + diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index b5d273646..27cd9b257 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1049,6 +1049,27 @@ class DBRegion_TestClass < TestBase end + # Some filters + def test_holesfilter + + r = RBA::Region::new + r.insert(RBA::Box::new(RBA::Point::new(0, 0), RBA::Point::new(100, 200))) + rr = RBA::Region::new + rr.insert(RBA::Box::new(RBA::Point::new(10, 10), RBA::Point::new(20, 20))) + rr.insert(RBA::Box::new(RBA::Point::new(30, 30), RBA::Point::new(40, 40))) + r -= rr + + assert_equal(r.with_holes(0, false).to_s, "") + assert_equal(r.with_holes(0, true).to_s, "(0,0;0,200;100,200;100,0/10,10;20,10;20,20;10,20/30,30;40,30;40,40;30,40)") + assert_equal(rr.with_holes(0, false).to_s, "(10,10;10,20;20,20;20,10);(30,30;30,40;40,40;40,30)") + assert_equal(rr.with_holes(0, true).to_s, "") + assert_equal(rr.with_holes(2, false).to_s, "") + assert_equal(r.with_holes(1, 3, false).to_s, "(0,0;0,200;100,200;100,0/10,10;20,10;20,20;10,20/30,30;40,30;40,40;30,40)") + assert_equal(r.with_holes(2, 3, false).to_s, "(0,0;0,200;100,200;100,0/10,10;20,10;20,20;10,20/30,30;40,30;40,40;30,40)") + assert_equal(r.with_holes(1, 2, false).to_s, "") + + end + end load("test_epilogue.rb")