diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index 417351c16..0c3960bb7 100644 --- a/src/db/db/gsiDeclDbPolygon.cc +++ b/src/db/db/gsiDeclDbPolygon.cc @@ -61,7 +61,7 @@ struct simple_polygon_defs } } - static point_type point (C *c, size_t p) + static point_type point (const C *c, size_t p) { if (c->hull ().size () > p) { return c->hull ()[p]; @@ -70,12 +70,12 @@ struct simple_polygon_defs } } - static size_t num_points (C *c) + static size_t num_points (const C *c) { return c->hull ().size (); } - static bool is_empty (C *c) + static bool is_empty (const C *c) { return c->hull ().size () == 0; } @@ -868,17 +868,17 @@ struct polygon_defs } } - static size_t num_points (C *c) + static size_t num_points (const C *c) { return c->vertices (); } - static bool is_empty (C *c) + static bool is_empty (const C *c) { return c->vertices () == 0; } - static point_type point_hull (C *c, size_t p) + static point_type point_hull (const C *c, size_t p) { if (c->hull ().size () > p) { return c->hull ()[p]; @@ -887,7 +887,7 @@ struct polygon_defs } } - static point_type point_hole (C *c, unsigned int n, size_t p) + static point_type point_hole (const C *c, unsigned int n, size_t p) { if (c->holes () > n && c->contour (n + 1).size () > p) { return c->contour (n + 1)[p]; @@ -896,12 +896,12 @@ struct polygon_defs } } - static size_t num_points_hull (C *c) + static size_t num_points_hull (const C *c) { return c->hull ().size (); } - static size_t num_points_hole (C *c, unsigned int n) + static size_t num_points_hole (const C *c, unsigned int n) { return c->contour (n + 1).size (); } diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 43b809553..05064472b 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -47,6 +47,192 @@ namespace gsi { +// --------------------------------------------------------------------------------- +// PolygonFilter binding + +class PolygonFilterImpl + : public db::AllMustMatchFilter +{ +public: + PolygonFilterImpl () + { + mp_vars = &m_mag_and_orient; + m_wants_variants = true; + m_requires_raw_input = false; + } + + bool issue_selected (const db::Polygon &) const + { + return false; + } + + virtual bool selected (const db::Polygon &polygon) const + { + if (f_selected.can_issue ()) { + return f_selected.issue (&PolygonFilterImpl::issue_selected, polygon); + } else { + return db::AllMustMatchFilter::selected (polygon); + } + } + + virtual bool selected (const db::PolygonRef &polygon) const + { + db::Polygon p; + polygon.instantiate (p); + return selected (p); + } + + virtual const db::TransformationReducer *vars () const + { + return mp_vars; + } + + virtual bool requires_raw_input () const + { + return m_requires_raw_input; + } + + void set_requires_raw_input (bool f) + { + m_requires_raw_input = f; + } + + virtual bool wants_variants () const + { + return m_wants_variants; + } + + void set_wants_variants (bool f) + { + m_wants_variants = f; + } + + void is_isotropic () + { + mp_vars = &m_mag; + } + + void is_scale_invariant () + { + mp_vars = &m_orientation; + } + + void is_isotropic_and_scale_invariant () + { + mp_vars = 0; + } + +public: + const db::TransformationReducer *mp_vars; + db::OrientationReducer m_orientation; + db::MagnificationReducer m_mag; + db::MagnificationAndOrientationReducer m_mag_and_orient; + bool m_requires_raw_input; + bool m_wants_variants; + + gsi::Callback f_selected; +}; + +Class decl_PluginFactory ("db", "PolygonFilter", + method ("requires_raw_input?", &PolygonFilterImpl::requires_raw_input, + "@brief Gets a value indicating whether the filter needs raw (unmerged) input\n" + "See \\requires_raw_input= for details.\n" + ) + + method ("requires_raw_input=", &PolygonFilterImpl::set_requires_raw_input, gsi::arg ("flag"), + "@brief Sets a value indicating whether the filter needs raw (unmerged) input\n" + "This flag must be set before using this filter. It tells the filter implementation whether the " + "filter wants to have raw input (unmerged). The default value is 'false', meaning that\n" + "the filter will receive merged polygons. Setting this value to false potentially saves some\n" + "CPU time needed for merging the polygons.\n" + ) + + method ("wants_variants?", &PolygonFilterImpl::wants_variants, + "@brief Gets a value indicating whether the filter prefers cell variants\n" + "See \\wants_variants= for details.\n" + ) + + method ("wants_variants=", &PolygonFilterImpl::set_wants_variants, gsi::arg ("flag"), + "@brief Sets a value indicating whether the filter prefers cell variants\n" + "This flag must be set before using this filter. It tells the filter implementation whether cell " + "variants should be created (true, the default) or shape propagation will be applied (false).\n" + "\n" + "This decision needs to be make if the filter indicates that it will deliver different results\n" + "for scaled or rotated versions of the cell (see \\is_isotropic and the other hints). If a cell\n" + "is present with different respective qualities - as seen from the top cell - these instances\n" + "need to be differentiated. Cell variant formation is one way, shape propagation the other way.\n" + "Typically, cell variant formation is less expensive, but the hierarchy will be modified internally." + ) + + method ("is_isotropic", &PolygonFilterImpl::is_isotropic, + "@brief Indicates that the filter has isotropic properties\n" + "Call this method before using the filter to indicate that the selection is independent of " + "the orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in " + "hierarchical mode.\n" + "\n" + "Examples for isotropic filters are area or perimeter filters." + ) + + method ("is_scale_invariant", &PolygonFilterImpl::is_scale_invariant, + "@brief Indicates that the filter is scale invariant\n" + "Call this method before using the filter to indicate that the selection is independent of " + "the scale of the shape. This helps the filter algorithm optimizing the filter run, specifically in " + "hierarchical mode.\n" + "\n" + "An example for a scale invariant filter is the bounding box aspect ratio (height/width) filter." + ) + + method ("is_isotropic_and_scale_invariant", &PolygonFilterImpl::is_isotropic_and_scale_invariant, + "@brief Indicates that the filter is isotropic and scale invariant\n" + "Call this method before using the filter to indicate that the selection is independent of " + "the scale and orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in " + "hierarchical mode.\n" + "\n" + "An example for such a filter is the rectangle selector." + ) + + callback ("selected", &PolygonFilterImpl::issue_selected, &PolygonFilterImpl::f_selected, gsi::arg ("polygon"), + "@brief Selects a polygon\n" + "This method is the actual payload. It needs to be reimplemented in a derived class.\n" + "It needs to analyze the polygon and return 'true' if it should be kept and 'false' if it should be discarded." + ), + "@brief A generic polygon filter adaptor\n" + "\n" + "Polygon filters are an efficient way to filter polygons from a Region. To apply a filter, derive your own " + "filter class and pass an instance to \\Region#filter or \\Region#filtered method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the filter's 'selected' method.\n" + "Based on the result of this evaluation, the polygon is kept or discarded.\n" + "\n" + "The magic happens when deep mode regions are involved. In that case, the filter will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the filter behaves. You " + "need to configure the filter by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using the filter.\n" + "\n" + "You can skip this step, but the filter algorithm will assume the worst case then. This usually leads to cell variant " + "formation which is not always desired and blows up the hierarchy.\n" + "\n" + "Here is some example that filters triangles:" + "\n" + "@code\n" + "class TriangleFilter < RBA::PolygonFilter\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # the triangle nature is not dependent on the scale or orientation\n" + " end\n" + " \n" + " # Select only triangles\n" + " def selected(polygon)\n" + " return polygon.holes == 0 && polygon.num_points == 3\n" + " end\n" + "\n" + "end\n" + "\n" + "region = ... # some Region\n" + "triangles_only = region.filtered(TriangleFilter::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + + +// --------------------------------------------------------------------------------- +// Region binding + static inline std::vector as_2region_vector (const std::pair &rp) { std::vector res; @@ -318,6 +504,16 @@ static db::Edges extent_refs_edges (const db::Region *r, double fx1, double fy1, return r->processed (db::RelativeExtentsAsEdges (fx1, fy1, fx2, fy2)); } +static db::Region filtered (const db::Region *r, const PolygonFilterImpl *f) +{ + return r->filtered (*f); +} + +static void filter (db::Region *r, const PolygonFilterImpl *f) +{ + r->filter (*f); +} + static db::Region with_perimeter1 (const db::Region *r, db::Region::perimeter_type perimeter, bool inverse) { db::RegionPerimeterFilter f (perimeter, perimeter + 1, inverse); @@ -2321,6 +2517,18 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This method has been introduced in version 0.28.\n" ) + + method_ext ("filter", &filter, gsi::arg ("filter"), + "@brief Applies a generic filter in place (replacing the polygons from the Region)\n" + "See \\PolygonFilter for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("filtered", &filtered, gsi::arg ("filtered"), + "@brief Applies a generic filter and returns a filtered copy\n" + "See \\PolygonFilter for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("rectangles", &rectangles, "@brief Returns all polygons which are rectangles\n" "This method returns all polygons in self which are rectangles." diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index de204bc3d..2f33b63df 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -30,6 +30,20 @@ def csort(s) s.split(/(?<=\));(?=\()/).sort.join(";") end +class TriangleFilter < RBA::PolygonFilter + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # the triangle nature is not dependent on the scale or orientation + end + + # Select only triangles + def selected(polygon) + return polygon.holes == 0 && polygon.num_points == 3 + end + +end + class DBRegion_TestClass < TestBase # Basics @@ -1188,6 +1202,34 @@ class DBRegion_TestClass < TestBase end + # Generic filters + def test_generic_filters + + # Some basic tests for the filter class + + f = TriangleFilter::new + assert_equal(f.wants_variants?, true) + f.wants_variants = false + assert_equal(f.wants_variants?, false) + assert_equal(f.requires_raw_input, false) + f.requires_raw_input = false + assert_equal(f.requires_raw_input, false) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + region = RBA::Region::new + + region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]])) + region.insert(RBA::Box::new(200, 0, 300, 100)) + + assert_equal(region.filtered(TriangleFilter::new).to_s, "(0,0;100,100;100,0)") + + end + end load("test_epilogue.rb")