mirror of https://github.com/KLayout/klayout.git
Introducing 'with_holes' and 'without_holes' in DRC and RBA::Region.
This commit is contained in:
parent
94e6f0f7a6
commit
a9fa5d73f9
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue