Introducing 'with_holes' and 'without_holes' in DRC and RBA::Region.

This commit is contained in:
Matthias Koefferlein 2021-02-08 20:59:17 +01:00
parent 94e6f0f7a6
commit a9fa5d73f9
7 changed files with 218 additions and 1 deletions

View File

@ -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

View File

@ -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
*

View File

@ -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<size_t> (), max.is_nil () ? std::numeric_limits <size_t>::max () : max.to<size_t> (), 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<db::Region> 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 "

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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")