From b34f539fe1228f092c882e5b31c1e0d850122529 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 25 Jan 2024 23:10:12 +0100 Subject: [PATCH 01/11] Generic filter for polygons --- src/db/db/gsiDeclDbPolygon.cc | 18 +-- src/db/db/gsiDeclDbRegion.cc | 208 ++++++++++++++++++++++++++++++++++ testdata/ruby/dbRegionTest.rb | 42 +++++++ 3 files changed, 259 insertions(+), 9 deletions(-) diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index 417351c162..0c3960bb74 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 43b8095534..05064472b5 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 de204bc3d0..2f33b63df8 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 @@ def test_check_with_properties 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") From 0b77ef996b0cdbeea882fbaeb67b1ab0035c42d3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 25 Jan 2024 23:27:39 +0100 Subject: [PATCH 02/11] Removed unneccessary code --- src/db/db/dbDeepRegion.h | 2 -- src/db/db/dbRegionDelegate.h | 56 ------------------------------------ 2 files changed, 58 deletions(-) diff --git a/src/db/db/dbDeepRegion.h b/src/db/db/dbDeepRegion.h index 1bef5efb18..229b2e696a 100644 --- a/src/db/db/dbDeepRegion.h +++ b/src/db/db/dbDeepRegion.h @@ -182,8 +182,6 @@ class DB_PUBLIC DeepRegion std::pair and_and_not_with (const DeepRegion *other, PropertyConstraint property_constraint) const; DeepRegion *apply_filter (const PolygonFilterBase &filter) const; - template OutputContainer *processed_impl (const polygon_processor &filter) const; - template void configure_proc (Proc &proc) const { diff --git a/src/db/db/dbRegionDelegate.h b/src/db/db/dbRegionDelegate.h index ab8597646c..b1c32aad05 100644 --- a/src/db/db/dbRegionDelegate.h +++ b/src/db/db/dbRegionDelegate.h @@ -106,62 +106,6 @@ class DB_PUBLIC PolygonFilterBase virtual bool wants_variants () const = 0; }; -/** - * @brief A template base class for polygon processors - * - * A polygon processor can turn a polygon into something else. - */ -template -class DB_PUBLIC polygon_processor -{ -public: - /** - * @brief Constructor - */ - polygon_processor () { } - - /** - * @brief Destructor - */ - virtual ~polygon_processor () { } - - /** - * @brief Performs the actual processing - * This method will take the input polygon from "polygon" and puts the results into "res". - * "res" can be empty - in this case, the polygon will be skipped. - */ - virtual void process (const db::Polygon &polygon, std::vector &res) const = 0; - - /** - * @brief Returns the transformation reducer for building cell variants - * This method may return 0. In this case, not cell variants are built. - */ - virtual const TransformationReducer *vars () const = 0; - - /** - * @brief Returns true, if the result of this operation can be regarded "merged" always. - */ - virtual bool result_is_merged () const = 0; - - /** - * @brief Returns true, if the result of this operation must not be merged. - * This feature can be used, if the result represents "degenerated" objects such - * as point-like edges. These must not be merged. Otherwise they disappear. - */ - virtual bool result_must_not_be_merged () const = 0; - - /** - * @brief Returns true, if the processor wants raw (not merged) input - */ - virtual bool requires_raw_input () const = 0; - - /** - * @brief Returns true, if the processor wants to build variants - * If not true, the processor accepts shape propagation as variant resolution. - */ - virtual bool wants_variants () const = 0; -}; - typedef shape_collection_processor PolygonProcessorBase; typedef shape_collection_processor PolygonToEdgeProcessorBase; typedef shape_collection_processor PolygonToEdgePairProcessorBase; From 2dca4158f2a391dd56d4065f029a8e4068ac2715 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Jan 2024 10:30:03 +0100 Subject: [PATCH 03/11] Some refactoring. --- src/db/db/gsiDeclDbRegion.cc | 193 +++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 79 deletions(-) diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 05064472b5..b42f8874b1 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -48,46 +48,28 @@ namespace gsi { // --------------------------------------------------------------------------------- -// PolygonFilter binding +// Generic shape filter declarations -class PolygonFilterImpl - : public db::AllMustMatchFilter +template +class shape_filter_impl + : public Base { public: - PolygonFilterImpl () + shape_filter_impl () { 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 + // overrides virtual method + const db::TransformationReducer *vars () const { return mp_vars; } - virtual bool requires_raw_input () const + // maybe overrides virtual method + bool requires_raw_input () const { return m_requires_raw_input; } @@ -97,7 +79,8 @@ class PolygonFilterImpl m_requires_raw_input = f; } - virtual bool wants_variants () const + // overrides virtual method + bool wants_variants () const { return m_wants_variants; } @@ -122,68 +105,120 @@ class PolygonFilterImpl mp_vars = 0; } -public: + static gsi::Methods method_decls (bool with_requires_raw_input) + { + gsi::Methods decls; + + if (with_requires_raw_input) { + decls = + method ("requires_raw_input?", &shape_filter_impl::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=", &shape_filter_impl::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 ('merged semantics').\n" + "\n" + "Setting this value to false potentially saves some CPU time needed for merging the polygons.\n" + "Also, raw input means that strange shapes such as dot-like edges, self-overlapping polygons, " + "empty or degenerated polygons are preserved." + ); + } + + decls += + method ("wants_variants?", &shape_filter_impl::wants_variants, + "@brief Gets a value indicating whether the filter prefers cell variants\n" + "See \\wants_variants= for details.\n" + ) + + method ("wants_variants=", &shape_filter_impl::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 for hierarchical applications (deep mode). " + "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 made, if the filter indicates that it will deliver different results\n" + "for scaled or rotated versions of the shape (see \\is_isotropic and the other hints). If a cell\n" + "is present with different qualities - as seen from the top cell - the respective 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." + ) + + method ("is_isotropic", &shape_filter_impl::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 (polygon) filters are area or perimeter filters. The area or perimeter of a polygon " + "depends on the scale, but not on the orientation of the polygon." + ) + + method ("is_scale_invariant", &shape_filter_impl::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 (polygon) filter is the bounding box aspect ratio (height/width) filter. " + "The definition of heigh and width depends on the orientation, but the ratio is independent on scale." + ) + + method ("is_isotropic_and_scale_invariant", &shape_filter_impl::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 (polygon) filter is the square selector. Whether a polygon is a square or not does not depend on " + "the polygon's orientation nor scale." + ); + + return decls; + } + +private: 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; +}; + +// --------------------------------------------------------------------------------- +// PolygonFilter binding + +class PolygonFilterImpl + : public shape_filter_impl +{ +public: + PolygonFilterImpl () { } + + 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); + } 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." - ) + + PolygonFilterImpl::method_decls (true) + 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" From 9fbc926d679afdc0b596e0d43ce90a996e0f201d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Jan 2024 11:49:15 +0100 Subject: [PATCH 04/11] Generic filters also for edge pair, edge and text collections --- src/db/db/gsiDeclDbContainerHelpers.h | 138 +++++++++++++++++++++++++ src/db/db/gsiDeclDbEdgePairs.cc | 100 +++++++++++++++++- src/db/db/gsiDeclDbEdges.cc | 111 ++++++++++++++++++++ src/db/db/gsiDeclDbRegion.cc | 140 +------------------------- src/db/db/gsiDeclDbTexts.cc | 99 ++++++++++++++++++ testdata/ruby/dbEdgePairsTest.rb | 43 ++++++++ testdata/ruby/dbEdgesTest.rb | 47 +++++++++ testdata/ruby/dbRegionTest.rb | 3 + testdata/ruby/dbTextsTest.rb | 45 +++++++++ 9 files changed, 586 insertions(+), 140 deletions(-) diff --git a/src/db/db/gsiDeclDbContainerHelpers.h b/src/db/db/gsiDeclDbContainerHelpers.h index e16e91be8b..ee6112948f 100644 --- a/src/db/db/gsiDeclDbContainerHelpers.h +++ b/src/db/db/gsiDeclDbContainerHelpers.h @@ -25,6 +25,7 @@ #define HDR_gsiDeclDbContainerHelpers #include "dbPropertiesRepository.h" +#include "dbCellVariants.h" #include "tlVariant.h" #include "gsiDecl.h" @@ -100,6 +101,143 @@ make_property_methods () ); } +// --------------------------------------------------------------------------------- +// Generic shape filter declarations + +template +class shape_filter_impl + : public FilterBase +{ +public: + shape_filter_impl () + { + mp_vars = &m_mag_and_orient; + m_wants_variants = true; + m_requires_raw_input = false; + } + + // overrides virtual method + virtual const db::TransformationReducer *vars () const + { + return mp_vars; + } + + // maybe overrides virtual method + virtual bool requires_raw_input () const + { + return m_requires_raw_input; + } + + void set_requires_raw_input (bool f) + { + m_requires_raw_input = f; + } + + // overrides virtual method + 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; + } + + static gsi::Methods method_decls (bool with_requires_raw_input) + { + gsi::Methods decls; + + if (with_requires_raw_input) { + decls = + method ("requires_raw_input?", &shape_filter_impl::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=", &shape_filter_impl::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 ('merged semantics').\n" + "\n" + "Setting this value to false potentially saves some CPU time needed for merging the polygons.\n" + "Also, raw input means that strange shapes such as dot-like edges, self-overlapping polygons, " + "empty or degenerated polygons are preserved." + ); + } + + decls += + method ("wants_variants?", &shape_filter_impl::wants_variants, + "@brief Gets a value indicating whether the filter prefers cell variants\n" + "See \\wants_variants= for details.\n" + ) + + method ("wants_variants=", &shape_filter_impl::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 for hierarchical applications (deep mode). " + "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 made, if the filter indicates that it will deliver different results\n" + "for scaled or rotated versions of the shape (see \\is_isotropic and the other hints). If a cell\n" + "is present with different qualities - as seen from the top cell - the respective 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." + ) + + method ("is_isotropic", &shape_filter_impl::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 (polygon) filters are area or perimeter filters. The area or perimeter of a polygon " + "depends on the scale, but not on the orientation of the polygon." + ) + + method ("is_scale_invariant", &shape_filter_impl::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 (polygon) filter is the bounding box aspect ratio (height/width) filter. " + "The definition of heigh and width depends on the orientation, but the ratio is independent on scale." + ) + + method ("is_isotropic_and_scale_invariant", &shape_filter_impl::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 (polygon) filter is the square selector. Whether a polygon is a square or not does not depend on " + "the polygon's orientation nor scale." + ); + + return decls; + } + +private: + 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; +}; + } #endif diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 870991c4dc..3f56397200 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -36,7 +36,83 @@ namespace gsi { -static db::EdgePairs *new_v () +// --------------------------------------------------------------------------------- +// EdgePairFilter binding + +class EdgePairFilterImpl + : public shape_filter_impl +{ +public: + EdgePairFilterImpl () { } + + bool issue_selected (const db::EdgePair &) const + { + return false; + } + + virtual bool selected (const db::EdgePair &edge_pair) const + { + if (f_selected.can_issue ()) { + return f_selected.issue (&EdgePairFilterImpl::issue_selected, edge_pair); + } else { + return db::EdgePairFilterBase::selected (edge_pair); + } + } + + gsi::Callback f_selected; +}; + +Class decl_EdgePairFilterImpl ("db", "EdgePairFilter", + EdgePairFilterImpl::method_decls (false) + + callback ("selected", &EdgePairFilterImpl::issue_selected, &EdgePairFilterImpl::f_selected, gsi::arg ("text"), + "@brief Selects an edge pair\n" + "This method is the actual payload. It needs to be reimplemented in a derived class.\n" + "It needs to analyze the edge pair and return 'true' if it should be kept and 'false' if it should be discarded." + ), + "@brief A generic edge pair filter adaptor\n" + "\n" + "EdgePair filters are an efficient way to filter edge pairs from a EdgePairs collection. To apply a filter, derive your own " + "filter class and pass an instance to \\EdgePairs#filter or \\EdgePairs#filtered method.\n" + "\n" + "Conceptually, these methods take each edge pair from the collection and present it to the filter's 'selected' method.\n" + "Based on the result of this evaluation, the edge pair is kept or discarded.\n" + "\n" + "The magic happens when deep mode edge pair collections 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 edge pairs where the edges are perpendicular:" + "\n" + "@code\n" + "class PerpendicularEdgesFilter < RBA::EdgePairFilter\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n" + " end\n" + " \n" + " # Select edge pairs where the edges are perpendicular\n" + " def selected(edge_pair)\n" + " return edge_pair.first.d.sprod_sign(edge_pair.second.d) == 0\n" + " end\n" + "\n" + "end\n" + "\n" + "edge_pairs = ... # some EdgePairs object\n" + "perpendicular_only = edge_pairs.filtered(PerpendicularEdgesFilter::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +// --------------------------------------------------------------------------------- +// EdgePairs binding + +static db::EdgePairs *new_v () { return new db::EdgePairs (); } @@ -181,6 +257,16 @@ static size_t id (const db::EdgePairs *ep) return tl::id_of (ep->delegate ()); } +static db::EdgePairs filtered (const db::EdgePairs *r, const EdgePairFilterImpl *f) +{ + return r->filtered (*f); +} + +static void filter (db::EdgePairs *r, const EdgePairFilterImpl *f) +{ + r->filter (*f); +} + static db::EdgePairs with_distance1 (const db::EdgePairs *r, db::EdgePairs::distance_type length, bool inverse) { db::EdgePairFilterByDistance ef (length, length + 1, inverse); @@ -619,6 +705,18 @@ Class decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs", "The boxes will not be merged, so it is possible to determine overlaps " "of these boxes for example.\n" ) + + method_ext ("filter", &filter, gsi::arg ("filter"), + "@brief Applies a generic filter in place (replacing the edge pairs from the EdgePair collection)\n" + "See \\EdgePairFilter 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 \\EdgePairFilter for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"), "@brief Filters the edge pairs by length of one of their edges\n" "Filters the edge pairs in the edge pair collection by length of at least one of their edges. If \"inverse\" is false, only " diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 718b6d16b8..8640749e4f 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -37,6 +37,95 @@ namespace gsi { +// --------------------------------------------------------------------------------- +// EdgeFilter binding + +class EdgeFilterImpl + : public shape_filter_impl +{ +public: + EdgeFilterImpl () { } + + bool issue_selected (const db::Edge &) const + { + return false; + } + + virtual bool selected (const db::Edge &edge) const + { + if (f_selected.can_issue ()) { + return f_selected.issue (&EdgeFilterImpl::issue_selected, edge); + } else { + return db::EdgeFilterBase::selected (edge); + } + } + + // Returns true if all edges match the criterion + virtual bool selected (const std::unordered_set &edges) const + { + for (std::unordered_set::const_iterator e = edges.begin (); e != edges.end (); ++e) { + if (! selected (*e)) { + return false; + } + } + return true; + } + + gsi::Callback f_selected; +}; + +Class decl_EdgeFilterImpl ("db", "EdgeFilter", + EdgeFilterImpl::method_decls (true) + + callback ("selected", &EdgeFilterImpl::issue_selected, &EdgeFilterImpl::f_selected, gsi::arg ("edge"), + "@brief Selects an edge\n" + "This method is the actual payload. It needs to be reimplemented in a derived class.\n" + "It needs to analyze the edge and return 'true' if it should be kept and 'false' if it should be discarded." + ), + "@brief A generic edge filter adaptor\n" + "\n" + "Edge filters are an efficient way to filter edge from a Edges collection. To apply a filter, derive your own " + "filter class and pass an instance to \\Edges#filter or \\Edges#filtered method.\n" + "\n" + "Conceptually, these methods take each edge from the collection and present it to the filter's 'selected' method.\n" + "Based on the result of this evaluation, the edge is kept or discarded.\n" + "\n" + "The magic happens when deep mode edge collections 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 edges parallel to a given one:" + "\n" + "@code\n" + "class ParallelFilter < RBA::EdgeFilter\n" + "\n" + " # Constructor\n" + " def initialize(ref_edge)\n" + " self.is_scale_invariant # orientation matters, but scale does not\n" + " @ref_edge = ref_edge\n" + " end\n" + " \n" + " # Select only parallel ones\n" + " def selected(edge)\n" + " return edge.is_parallel?(@ref_edge)\n" + " end\n" + "\n" + "end\n" + "\n" + "edges = ... # some Edges object\n" + "ref_edge = ... # some Edge\n" + "parallel_only = edges.filtered(ParallelFilter::new(ref_edge))\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +// --------------------------------------------------------------------------------- +// Edges binding + static inline std::vector as_2edges_vector (const std::pair &rp) { std::vector res; @@ -204,6 +293,16 @@ static db::Edges moved_xy (const db::Edges *r, db::Coord x, db::Coord y) return r->transformed (db::Disp (db::Vector (x, y))); } +static db::Edges filtered (const db::Edges *r, const EdgeFilterImpl *f) +{ + return r->filtered (*f); +} + +static void filter (db::Edges *r, const EdgeFilterImpl *f) +{ + r->filter (*f); +} + static db::Edges with_length1 (const db::Edges *r, db::Edges::distance_type length, bool inverse) { db::EdgeLengthFilter f (length, length + 1, inverse); @@ -621,6 +720,18 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "\n" "This method has been introduced in version 0.26." ) + + method_ext ("filter", &filter, gsi::arg ("filter"), + "@brief Applies a generic filter in place (replacing the edges from the Edges collection)\n" + "See \\EdgeFilter 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 \\EdgeFilter for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"), "@brief Filters the edges by length\n" "Filters the edges in the edge collection by length. If \"inverse\" is false, only " diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index b42f8874b1..b6c00648c2 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -47,143 +47,6 @@ namespace gsi { -// --------------------------------------------------------------------------------- -// Generic shape filter declarations - -template -class shape_filter_impl - : public Base -{ -public: - shape_filter_impl () - { - mp_vars = &m_mag_and_orient; - m_wants_variants = true; - m_requires_raw_input = false; - } - - // overrides virtual method - const db::TransformationReducer *vars () const - { - return mp_vars; - } - - // maybe overrides virtual method - bool requires_raw_input () const - { - return m_requires_raw_input; - } - - void set_requires_raw_input (bool f) - { - m_requires_raw_input = f; - } - - // overrides virtual method - 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; - } - - static gsi::Methods method_decls (bool with_requires_raw_input) - { - gsi::Methods decls; - - if (with_requires_raw_input) { - decls = - method ("requires_raw_input?", &shape_filter_impl::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=", &shape_filter_impl::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 ('merged semantics').\n" - "\n" - "Setting this value to false potentially saves some CPU time needed for merging the polygons.\n" - "Also, raw input means that strange shapes such as dot-like edges, self-overlapping polygons, " - "empty or degenerated polygons are preserved." - ); - } - - decls += - method ("wants_variants?", &shape_filter_impl::wants_variants, - "@brief Gets a value indicating whether the filter prefers cell variants\n" - "See \\wants_variants= for details.\n" - ) + - method ("wants_variants=", &shape_filter_impl::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 for hierarchical applications (deep mode). " - "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 made, if the filter indicates that it will deliver different results\n" - "for scaled or rotated versions of the shape (see \\is_isotropic and the other hints). If a cell\n" - "is present with different qualities - as seen from the top cell - the respective 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." - ) + - method ("is_isotropic", &shape_filter_impl::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 (polygon) filters are area or perimeter filters. The area or perimeter of a polygon " - "depends on the scale, but not on the orientation of the polygon." - ) + - method ("is_scale_invariant", &shape_filter_impl::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 (polygon) filter is the bounding box aspect ratio (height/width) filter. " - "The definition of heigh and width depends on the orientation, but the ratio is independent on scale." - ) + - method ("is_isotropic_and_scale_invariant", &shape_filter_impl::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 (polygon) filter is the square selector. Whether a polygon is a square or not does not depend on " - "the polygon's orientation nor scale." - ); - - return decls; - } - -private: - 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; -}; - // --------------------------------------------------------------------------------- // PolygonFilter binding @@ -217,7 +80,7 @@ class PolygonFilterImpl gsi::Callback f_selected; }; -Class decl_PluginFactory ("db", "PolygonFilter", +Class decl_PolygonFilterImpl ("db", "PolygonFilter", PolygonFilterImpl::method_decls (true) + callback ("selected", &PolygonFilterImpl::issue_selected, &PolygonFilterImpl::f_selected, gsi::arg ("polygon"), "@brief Selects a polygon\n" @@ -264,7 +127,6 @@ Class decl_PluginFactory ("db", "PolygonFilter", "This class has been introduced in version 0.29.\n" ); - // --------------------------------------------------------------------------------- // Region binding diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index b105c5d82a..d186c2c324 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -33,6 +33,83 @@ namespace gsi { +// --------------------------------------------------------------------------------- +// TextFilter binding + +class TextFilterImpl + : public shape_filter_impl +{ +public: + TextFilterImpl () { } + + bool issue_selected (const db::Text &) const + { + return false; + } + + virtual bool selected (const db::Text &text) const + { + if (f_selected.can_issue ()) { + return f_selected.issue (&TextFilterImpl::issue_selected, text); + } else { + return db::TextFilterBase::selected (text); + } + } + + gsi::Callback f_selected; +}; + +Class decl_TextFilterImpl ("db", "TextFilter", + TextFilterImpl::method_decls (false) + + callback ("selected", &TextFilterImpl::issue_selected, &TextFilterImpl::f_selected, gsi::arg ("text"), + "@brief Selects a text\n" + "This method is the actual payload. It needs to be reimplemented in a derived class.\n" + "It needs to analyze the text and return 'true' if it should be kept and 'false' if it should be discarded." + ), + "@brief A generic text filter adaptor\n" + "\n" + "Text filters are an efficient way to filter texts from a Texts collection. To apply a filter, derive your own " + "filter class and pass an instance to \\Texts#filter or \\Texts#filtered method.\n" + "\n" + "Conceptually, these methods take each text from the collection and present it to the filter's 'selected' method.\n" + "Based on the result of this evaluation, the text is kept or discarded.\n" + "\n" + "The magic happens when deep mode text collections 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 texts with a given string length:" + "\n" + "@code\n" + "class TextStringLengthFilter < RBA::TextFilter\n" + "\n" + " # Constructor\n" + " def initialize(string_length)\n" + " self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n" + " @string_length = string_length\n" + " end\n" + " \n" + " # Select texts with given string length\n" + " def selected(text)\n" + " return text.string.size == @string_length\n" + " end\n" + "\n" + "end\n" + "\n" + "texts = ... # some Texts object\n" + "with_length_3 = edges.filtered(TextStringLengthFilter::new(3))\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +// --------------------------------------------------------------------------------- +// Texts binding + static db::Texts *new_v () { return new db::Texts (); @@ -152,6 +229,16 @@ static size_t id (const db::Texts *t) return tl::id_of (t->delegate ()); } +static db::Texts filtered (const db::Texts *r, const TextFilterImpl *f) +{ + return r->filtered (*f); +} + +static void filter (db::Texts *r, const TextFilterImpl *f) +{ + r->filter (*f); +} + static db::Texts with_text (const db::Texts *r, const std::string &text, bool inverse) { db::TextStringFilter f (text, inverse); @@ -401,6 +488,18 @@ Class decl_Texts (decl_dbShapeCollection, "db", "Texts", "@brief Converts the edge pairs to polygons\n" "This method creates polygons from the texts. This is equivalent to calling \\extents." ) + + method_ext ("filter", &filter, gsi::arg ("filter"), + "@brief Applies a generic filter in place (replacing the texts from the Texts collection)\n" + "See \\TextFilter 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 \\TextFilter for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("with_text", with_text, gsi::arg ("text"), gsi::arg ("inverse"), "@brief Filter the text by text string\n" "If \"inverse\" is false, this method returns the texts with the given string.\n" diff --git a/testdata/ruby/dbEdgePairsTest.rb b/testdata/ruby/dbEdgePairsTest.rb index fa1ccad3c8..b5db1f8661 100644 --- a/testdata/ruby/dbEdgePairsTest.rb +++ b/testdata/ruby/dbEdgePairsTest.rb @@ -30,6 +30,20 @@ def csort(s) s.split(/(?<=\));(?=\()/).sort.join(";") end +class PerpendicularEdgesFilter < RBA::EdgePairFilter + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + end + + # Select edge pairs where the edges are perpendicular + def selected(edge_pair) + return edge_pair.first.d.sprod_sign(edge_pair.second.d) == 0 + end + +end + class DBEdgePairs_TestClass < TestBase # Basics @@ -331,6 +345,35 @@ def test_5 end + # Generic filters + def test_generic_filters + + # Some basic tests for the filter class + + f = PerpendicularEdgesFilter::new + assert_equal(f.wants_variants?, true) + f.wants_variants = false + assert_equal(f.wants_variants?, false) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + f = PerpendicularEdgesFilter::new + + edge_pairs = RBA::EdgePairs::new + edge_pairs.insert(RBA::EdgePair::new([0, 0, 100, 0], [0, 100, 0, 300 ])) + edge_pairs.insert(RBA::EdgePair::new([200, 0, 300, 0], [200, 100, 220, 300 ])) + + assert_equal(edge_pairs.filtered(f).to_s, "(0,0;100,0)/(0,100;0,300)") + assert_equal(edge_pairs.to_s, "(0,0;100,0)/(0,100;0,300);(200,0;300,0)/(200,100;220,300)") + edge_pairs.filter(f) + assert_equal(edge_pairs.to_s, "(0,0;100,0)/(0,100;0,300)") + + end + end diff --git a/testdata/ruby/dbEdgesTest.rb b/testdata/ruby/dbEdgesTest.rb index 1a0910a9df..5d3eb06604 100644 --- a/testdata/ruby/dbEdgesTest.rb +++ b/testdata/ruby/dbEdgesTest.rb @@ -30,6 +30,21 @@ def csort(s) s.split(/(?<=\));(?=\()/).sort.join(";") end +class ParallelFilter < RBA::EdgeFilter + + # Constructor + def initialize(ref_edge) + self.is_scale_invariant # orientation matters, but scale does not + @ref_edge = ref_edge + end + + # Select only parallel ones + def selected(edge) + return edge.is_parallel?(@ref_edge) + end + +end + class DBEdges_TestClass < TestBase # Basics @@ -749,6 +764,38 @@ def test_11 end + # Generic filters + def test_generic_filters + + # Some basic tests for the filter class + + f = ParallelFilter::new(RBA::Edge::new(0, 0, 100, 100)) + 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 + + f = ParallelFilter::new(RBA::Edge::new(0, 0, 100, 100)) + + edges = RBA::Edges::new + edges.insert(RBA::Edge::new(100, 0, 200, 100)) + edges.insert(RBA::Edge::new(100, 100, 100, 200)) + + assert_equal(edges.filtered(f).to_s, "(100,0;200,100)") + assert_equal(edges.to_s, "(100,0;200,100);(100,100;100,200)") + edges.filter(f) + assert_equal(edges.to_s, "(100,0;200,100)") + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 2f33b63df8..e0f2f07c99 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1227,6 +1227,9 @@ def test_generic_filters region.insert(RBA::Box::new(200, 0, 300, 100)) assert_equal(region.filtered(TriangleFilter::new).to_s, "(0,0;100,100;100,0)") + assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") + region.filter(TriangleFilter::new) + assert_equal(region.to_s, "(0,0;100,100;100,0)") end diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 7b9a65288d..385c50a00d 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -30,6 +30,21 @@ def csort(s) s.split(/(?<=\));(?=\()/).sort.join(";") end +class TextStringLengthFilter < RBA::TextFilter + + # Constructor + def initialize(string_length) + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + @string_length = string_length + end + + # Select texts with given string length + def selected(text) + return text.string.size == @string_length + end + +end + class DBTexts_TestClass < TestBase # Basics @@ -324,6 +339,36 @@ def test_5 end + # Generic filters + def test_generic_filters + + # Some basic tests for the filter class + + f = TextStringLengthFilter::new(3) + assert_equal(f.wants_variants?, true) + f.wants_variants = false + assert_equal(f.wants_variants?, false) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + f = TextStringLengthFilter::new(3) + + texts = RBA::Texts::new + texts.insert(RBA::Text::new("long", [ RBA::Trans::M45, 10, 0 ])) + texts.insert(RBA::Text::new("tla", [ RBA::Trans::R0, 0, 0 ])) + texts.insert(RBA::Text::new("00", [ RBA::Trans::R90, 0, 20 ])) + + assert_equal(texts.filtered(f).to_s, "('tla',r0 0,0)") + assert_equal(texts.to_s, "('long',m45 10,0);('tla',r0 0,0);('00',r90 0,20)") + texts.filter(f) + assert_equal(texts.to_s, "('tla',r0 0,0)") + + end + end From 1a126ede456c1c5c560f78fd8ccaa9d164a27823 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Jan 2024 13:05:09 +0100 Subject: [PATCH 05/11] Generic polygon to polygon processors --- src/db/db/dbShapeCollectionUtils.h | 3 + src/db/db/gsiDeclDbContainerHelpers.h | 214 ++++++++++++++++++++++++++ src/db/db/gsiDeclDbRegion.cc | 140 +++++++++++++++++ testdata/ruby/dbRegionTest.rb | 56 ++++++- 4 files changed, 411 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbShapeCollectionUtils.h b/src/db/db/dbShapeCollectionUtils.h index 0d2a448f90..c300bb0529 100644 --- a/src/db/db/dbShapeCollectionUtils.h +++ b/src/db/db/dbShapeCollectionUtils.h @@ -48,6 +48,9 @@ class DB_PUBLIC_TEMPLATE shape_collection_processor : public tl::Object { public: + typedef Shape shape_type; + typedef Result result_type; + /** * @brief Constructor */ diff --git a/src/db/db/gsiDeclDbContainerHelpers.h b/src/db/db/gsiDeclDbContainerHelpers.h index ee6112948f..278b793376 100644 --- a/src/db/db/gsiDeclDbContainerHelpers.h +++ b/src/db/db/gsiDeclDbContainerHelpers.h @@ -238,6 +238,220 @@ class shape_filter_impl bool m_wants_variants; }; +// --------------------------------------------------------------------------------- +// Generic shape processor declarations + +template +class shape_processor_impl + : public ProcessorBase +{ +public: + typedef typename ProcessorBase::shape_type shape_type; + typedef typename ProcessorBase::result_type result_type; + + shape_processor_impl () + { + mp_vars = &m_mag_and_orient; + m_wants_variants = true; + m_requires_raw_input = false; + m_result_is_merged = false; + m_result_must_not_be_merged = false; + } + + // overrides virtual method + virtual const db::TransformationReducer *vars () const + { + return mp_vars; + } + + // maybe overrides virtual method + virtual bool requires_raw_input () const + { + return m_requires_raw_input; + } + + void set_requires_raw_input (bool f) + { + m_requires_raw_input = f; + } + + // overrides virtual method + virtual bool wants_variants () const + { + return m_wants_variants; + } + + void set_wants_variants (bool f) + { + m_wants_variants = f; + } + + // overrides virtual method + virtual bool result_is_merged () const + { + return m_result_is_merged; + } + + void set_result_is_merged (bool f) + { + m_result_is_merged = f; + } + + // overrides virtual method + virtual bool result_must_not_be_merged () const + { + return m_result_must_not_be_merged; + } + + void set_result_must_not_be_merged (bool f) + { + m_result_must_not_be_merged = 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; + } + + virtual void process (const shape_type &shape, std::vector &res) const + { + res = do_process (shape); + } + + std::vector issue_do_process (const shape_type &) const + { + return std::vector (); + } + + std::vector do_process (const shape_type &shape) const + { + if (f_process.can_issue ()) { + return f_process.issue, const shape_type &> (&shape_processor_impl::issue_do_process, shape); + } else { + return issue_do_process (shape); + } + } + + gsi::Callback f_process; + + static gsi::Methods method_decls (bool with_merged_options) + { + gsi::Methods decls = + callback ("process", &shape_processor_impl::issue_do_process, &shape_processor_impl::f_process, gsi::arg ("shape"), + "@brief Processes a shape\n" + "This method is the actual payload. It needs to be reimplemented in a derived class.\n" + "If needs to process the input shape and deliver a list of output shapes.\n" + "The output list may be empty to entirely discard the input shape. It may also contain more than a single shape.\n" + "In that case, the number of total shapes may grow during application of the processor.\n" + ); + + if (with_merged_options) { + decls += + method ("requires_raw_input?", &shape_processor_impl::requires_raw_input, + "@brief Gets a value indicating whether the processor needs raw (unmerged) input\n" + "See \\requires_raw_input= for details.\n" + ) + + method ("requires_raw_input=", &shape_processor_impl::set_requires_raw_input, gsi::arg ("flag"), + "@brief Sets a value indicating whether the processor needs raw (unmerged) input\n" + "This flag must be set before using this processor. It tells the processor implementation whether the " + "processor wants to have raw input (unmerged). The default value is 'false', meaning that\n" + "the processor will receive merged polygons ('merged semantics').\n" + "\n" + "Setting this value to false potentially saves some CPU time needed for merging the polygons.\n" + "Also, raw input means that strange shapes such as dot-like edges, self-overlapping polygons, " + "empty or degenerated polygons are preserved." + ) + + method ("result_is_merged?", &shape_processor_impl::result_is_merged, + "@brief Gets a value indicating whether the processor delivers merged output\n" + "See \\result_is_merged= for details.\n" + ) + + method ("result_is_merged=", &shape_processor_impl::set_result_is_merged, gsi::arg ("flag"), + "@brief Sets a value indicating whether the processor delivers merged output\n" + "This flag must be set before using this processor. If the processor maintains the merged condition\n" + "by design (output is merged if input is), it is a good idea to set this predicate to 'true'.\n" + "This will avoid additional merge steps when the resulting collection is used in further operations\n" + "that need merged input\n." + ) + + method ("result_must_not_be_merged?", &shape_processor_impl::result_must_not_be_merged, + "@brief Gets a value indicating whether the processor's output must not be merged\n" + "See \\result_must_not_be_merged= for details.\n" + ) + + method ("result_must_not_be_merged=", &shape_processor_impl::set_result_must_not_be_merged, gsi::arg ("flag"), + "@brief Sets a value indicating whether the processor's output must not be merged\n" + "This flag must be set before using this processor. The processor can set this flag if it wants to\n" + "deliver shapes that must not be merged - e.g. point-like edges or strange or degenerated polygons.\n." + ); + } + + decls += + method ("wants_variants?", &shape_processor_impl::wants_variants, + "@brief Gets a value indicating whether the filter prefers cell variants\n" + "See \\wants_variants= for details.\n" + ) + + method ("wants_variants=", &shape_processor_impl::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 for hierarchical applications (deep mode). " + "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 made, if the filter indicates that it will deliver different results\n" + "for scaled or rotated versions of the shape (see \\is_isotropic and the other hints). If a cell\n" + "is present with different qualities - as seen from the top cell - the respective 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." + ) + + method ("is_isotropic", &shape_processor_impl::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 (polygon) processors are size or shrink operators. Size or shrink is not dependent " + "on orientation unless size or shrink needs to be different in x and y direction." + ) + + method ("is_scale_invariant", &shape_processor_impl::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 (polygon) processor is the rotation operator. Rotation is not depending on scale, " + "but on the original orientation as mirrored versions need to be rotated differently." + ) + + method ("is_isotropic_and_scale_invariant", &shape_processor_impl::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 (polygon) processor is the convex decomposition operator. The decomposition of a polygon into " + "convex parts is an operation that is not depending on scale nor orientation." + ); + + return decls; + } + +private: + 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; + bool m_result_is_merged; + bool m_result_must_not_be_merged; +}; + } #endif diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index b6c00648c2..83a9b5a62d 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -127,6 +127,102 @@ Class decl_PolygonFilterImpl ("db", "PolygonFilter", "This class has been introduced in version 0.29.\n" ); +// --------------------------------------------------------------------------------- +// PolygonProcessor binding + +Class > decl_PolygonProcessor ("db", "PolygonProcessor", + shape_processor_impl::method_decls (true), + "@brief A generic polygon processor adaptor\n" + "\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " + "processors class and pass an instance to \\Region#process or \\Region#processed method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input polygon.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " + "need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 shrinks every polygon to half of the size but does not change the position.\n" + "In this example the 'position' is defined by the center of the bounding box:" + "\n" + "@code\n" + "class ShrinkToHalfProcessor < RBA::PolygonProcessor\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # scale or orientation do not matter\n" + " end\n" + " \n" + " # Shrink to half size\n" + " def process(polygon)\n" + " shift = polygon.bbox.center - RBA::Point::new # shift vector\n" + " return [ (polygon.moved(-shift) * 0.5).moved(shift) ]\n" + " end\n" + "\n" + "end\n" + "\n" + "region = ... # some Region\n" + "shrinked_to_half = region.processed(ShrinkToHalf::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeProcessor", + shape_processor_impl::method_decls (true), + "@brief A generic polygon-to-edge processor adaptor\n" + "\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " + "processors class and pass an instance to \\Region#processed method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" + "The result of this call is a list of zero to many output edges derived from the input polygon.\n" + "The output edge collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " + "need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edges.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairProcessor", + shape_processor_impl::method_decls (true), + "@brief A generic polygon-to-edge-pair processor adaptor\n" + "\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " + "processors class and pass an instance to \\Region#processed method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" + "The result of this call is a list of zero to many output edge pairs derived from the input polygon.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " + "need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edge pairs.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + // --------------------------------------------------------------------------------- // Region binding @@ -411,6 +507,26 @@ static void filter (db::Region *r, const PolygonFilterImpl *f) r->filter (*f); } +static db::Region processed_pp (const db::Region *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_pp (db::Region *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::EdgePairs processed_pep (const db::Region *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static db::Edges processed_pe (const db::Region *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + static db::Region with_perimeter1 (const db::Region *r, db::Region::perimeter_type perimeter, bool inverse) { db::RegionPerimeterFilter f (perimeter, perimeter + 1, inverse); @@ -2426,6 +2542,30 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This method has been introduced in version 0.29.\n" ) + + method_ext ("process", &process_pp, gsi::arg ("process"), + "@brief Applies a generic polygon processor in place (replacing the polygons from the Region)\n" + "See \\PolygonProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_pp, gsi::arg ("processed"), + "@brief Applies a generic polygon processor and returns a processed copy\n" + "See \\PolygonProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_pep, gsi::arg ("processed"), + "@brief Applies a generic polygon-to-edge-pair processor and returns an edge pair collection with the results\n" + "See \\PolygonToEdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_pe, gsi::arg ("processed"), + "@brief Applies a generic polygon-to-edge processor and returns an edge collection with the results\n" + "See \\PolygonToEdgeProcessor 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 e0f2f07c99..9eea0ff388 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -44,6 +44,21 @@ def selected(polygon) end +class ShrinkToHalfProcessor < RBA::PolygonProcessor + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + # Shrink to half size + def process(polygon) + shift = polygon.bbox.center - RBA::Point::new # shift vector + return [ (polygon.moved(-shift) * 0.5).moved(shift) ] + end + +end + class DBRegion_TestClass < TestBase # Basics @@ -1212,8 +1227,8 @@ def test_generic_filters 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) + f.requires_raw_input = true + assert_equal(f.requires_raw_input, true) # Smoke test f.is_isotropic @@ -1233,6 +1248,43 @@ def test_generic_filters end + # Generic processors + def test_generic_processors + + # Some basic tests for the filter class + + f = ShrinkToHalfProcessor::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 = true + assert_equal(f.requires_raw_input, true) + assert_equal(f.result_is_merged, false) + f.result_is_merged = true + assert_equal(f.result_is_merged, true) + assert_equal(f.result_must_not_be_merged, false) + f.result_must_not_be_merged = true + assert_equal(f.result_must_not_be_merged, true) + + # 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.processed(ShrinkToHalfProcessor::new).to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") + assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") + region.process(ShrinkToHalfProcessor::new) + assert_equal(region.to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") + + end + end load("test_epilogue.rb") From bbb535a0bfa4c3766b9bd262a28b2e1b0641a705 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Jan 2024 13:09:48 +0100 Subject: [PATCH 06/11] Tests for polygon-to-edge and polygon-to-edge-pair processors --- testdata/ruby/dbRegionTest.rb | 62 +++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 9eea0ff388..87da79aa10 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -59,6 +59,34 @@ def process(polygon) end +class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(polygon) + box = polygon.bbox + return [ RBA::EdgePair::new([ box.left, box.bottom, box.left, box.top ], [ box.right, box.bottom, box.right, box.top ]) ] + end + +end + +class SomePolygonToEdgeProcessor < RBA::PolygonToEdgeProcessor + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(polygon) + box = polygon.bbox + return [ RBA::Edge::new(box.p1, box.p2) ] + end + +end + class DBRegion_TestClass < TestBase # Basics @@ -1249,9 +1277,9 @@ def test_generic_filters end # Generic processors - def test_generic_processors + def test_generic_processors_pp - # Some basic tests for the filter class + # Some basic tests for the processor class f = ShrinkToHalfProcessor::new assert_equal(f.wants_variants?, true) @@ -1285,6 +1313,36 @@ def test_generic_processors end + # Generic processors + def test_generic_processors_pep + + p = SomePolygonToEdgePairProcessor::new + + 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.processed(p).to_s, "(0,0;0,100)/(100,0;100,100);(200,0;200,100)/(300,0;300,100)") + assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") + + end + + # Generic processors + def test_generic_processors_pe + + p = SomePolygonToEdgeProcessor::new + + 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.processed(p).to_s, "(0,0;100,100);(200,0;300,100)") + assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") + + end + end load("test_epilogue.rb") From b7277631c3e2618d369384b307d1bfee385f850f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Jan 2024 15:19:44 +0100 Subject: [PATCH 07/11] Renaming of 'processor' to 'operator' to avoid name clash with EdgeProcessor, added edge operators. --- src/db/db/gsiDeclDbEdges.cc | 142 ++++++++++++++++++++++++++++++++++ src/db/db/gsiDeclDbRegion.cc | 48 ++++++------ testdata/ruby/dbEdgesTest.rb | 110 ++++++++++++++++++++++++++ testdata/ruby/dbRegionTest.rb | 16 ++-- 4 files changed, 284 insertions(+), 32 deletions(-) diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 8640749e4f..8816442170 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -123,6 +123,102 @@ Class decl_EdgeFilterImpl ("db", "EdgeFilter", "This class has been introduced in version 0.29.\n" ); +// --------------------------------------------------------------------------------- +// EdgeProcessor binding + +Class > decl_EdgeProcessorBase ("db", "EdgeOperator", + shape_processor_impl::method_decls (true), + "@brief A generic edge-to-polygon operator\n" + "\n" + "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " + "operator class and pass an instance to \\Edges#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edges derived from the input edge.\n" + "The output edge collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 shrinks every edge to half of the size, but does not change the position.\n" + "In this example the 'position' is defined by the center of the edge:" + "\n" + "@code\n" + "class ShrinkToHalf < RBA::EdgeOperator\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # scale or orientation do not matter\n" + " end\n" + " \n" + " # Shrink to half size\n" + " def process(edge)\n" + " shift = edge.bbox.center - RBA::Point::new # shift vector\n" + " return [ (edge.moved(-shift) * 0.5).moved(shift) ]\n" + " end\n" + "\n" + "end\n" + "\n" + "edges = ... # some Edges collection\n" + "shrinked_to_half = edges.processed(ShrinkToHalf::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgeToPolygonProcessor ("db", "EdgeToPolygonOperator", + shape_processor_impl::method_decls (true), + "@brief A generic edge-to-polygon operator\n" + "\n" + "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " + "operator class and pass an instance to \\Edges#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input edge.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeOperator class, with the exception that this incarnation has to deliver edges.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgeToEdgePairProcessor ("db", "EdgeToEdgePairOperator", + shape_processor_impl::method_decls (true), + "@brief A generic edge-to-edge-pair operator\n" + "\n" + "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " + "operator class and pass an instance to \\Edges#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edge pairs derived from the input edge.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeOperator class, with the exception that this incarnation has to deliver edge pairs.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + // --------------------------------------------------------------------------------- // Edges binding @@ -303,6 +399,28 @@ static void filter (db::Edges *r, const EdgeFilterImpl *f) r->filter (*f); } +static db::Edges processed_ee (const db::Edges *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_ee (db::Edges *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::EdgePairs processed_eep (const db::Edges *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static db::Region processed_ep (const db::Edges *r, const shape_processor_impl *f) +{ + db::Region out; + r->processed (out, *f); + return out; +} + static db::Edges with_length1 (const db::Edges *r, db::Edges::distance_type length, bool inverse) { db::EdgeLengthFilter f (length, length + 1, inverse); @@ -732,6 +850,30 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "\n" "This method has been introduced in version 0.29.\n" ) + + method_ext ("process", &process_ee, gsi::arg ("process"), + "@brief Applies a generic edge processor in place (replacing the edges from the Edges collection)\n" + "See \\EdgeProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_ee, gsi::arg ("processed"), + "@brief Applies a generic edge processor and returns a processed copy\n" + "See \\EdgeProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_eep, gsi::arg ("processed"), + "@brief Applies a generic edge-to-edge-pair processor and returns an edge pair collection with the results\n" + "See \\EdgeToEdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_ep, gsi::arg ("processed"), + "@brief Applies a generic edge-to-polygon processor and returns an edge collection with the results\n" + "See \\EdgeToPolygonProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"), "@brief Filters the edges by length\n" "Filters the edges in the edge collection by length. If \"inverse\" is false, only " diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 83a9b5a62d..a5d871e39c 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -130,20 +130,20 @@ Class decl_PolygonFilterImpl ("db", "PolygonFilter", // --------------------------------------------------------------------------------- // PolygonProcessor binding -Class > decl_PolygonProcessor ("db", "PolygonProcessor", +Class > decl_PolygonOperator ("db", "PolygonOperator", shape_processor_impl::method_decls (true), - "@brief A generic polygon processor adaptor\n" + "@brief A generic polygon operator\n" "\n" - "Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " - "processors class and pass an instance to \\Region#process or \\Region#processed method.\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " + "operator class and pass an instance to \\Region#process or \\Region#processed method.\n" "\n" - "Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" + "Conceptually, these methods take each polygon from the region and present it to the operators' 'process' method.\n" "The result of this call is a list of zero to many output polygons derived from the input polygon.\n" "The output region is the sum over all these individual results.\n" "\n" "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " - "and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " - "need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " "before using it.\n" "\n" "You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant " @@ -153,7 +153,7 @@ Class > decl_PolygonProcessor ("d "In this example the 'position' is defined by the center of the bounding box:" "\n" "@code\n" - "class ShrinkToHalfProcessor < RBA::PolygonProcessor\n" + "class ShrinkToHalf < RBA::PolygonOperator\n" "\n" " # Constructor\n" " def initialize\n" @@ -175,50 +175,50 @@ Class > decl_PolygonProcessor ("d "This class has been introduced in version 0.29.\n" ); -Class > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeProcessor", +Class > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeOperator", shape_processor_impl::method_decls (true), - "@brief A generic polygon-to-edge processor adaptor\n" + "@brief A generic polygon-to-edge operator\n" "\n" - "Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " - "processors class and pass an instance to \\Region#processed method.\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " + "operator class and pass an instance to \\Region#processed method.\n" "\n" - "Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" + "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n" "The result of this call is a list of zero to many output edges derived from the input polygon.\n" "The output edge collection is the sum over all these individual results.\n" "\n" "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " - "and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " - "need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " "before using it.\n" "\n" "You can skip this step, but the processor 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" - "For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edges.\n" + "For a basic example see the \\PolygonOperator class, with the exception that this incarnation has to deliver edges.\n" "\n" "This class has been introduced in version 0.29.\n" ); -Class > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairProcessor", +Class > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairOperator", shape_processor_impl::method_decls (true), - "@brief A generic polygon-to-edge-pair processor adaptor\n" + "@brief A generic polygon-to-edge-pair operator\n" "\n" - "Polygon processors are an efficient way to process polygons from a Region. To apply a processors, derive your own " - "processors class and pass an instance to \\Region#processed method.\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " + "operator class and pass an instance to \\Region#processed method.\n" "\n" - "Conceptually, these methods take each polygon from the region and present it to the processor's 'process' method.\n" + "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n" "The result of this call is a list of zero to many output edge pairs derived from the input polygon.\n" "The output edge pair collection is the sum over all these individual results.\n" "\n" "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " - "and exploit the hierarchical compression if possible. It needs to know however, how the processor behaves. You " - "need to configure the processor by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " "before using it.\n" "\n" "You can skip this step, but the processor 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" - "For a basic example see the \\PolygonProcessor class, with the exception that this incarnation has to deliver edge pairs.\n" + "For a basic example see the \\PolygonOperator class, with the exception that this incarnation has to deliver edge pairs.\n" "\n" "This class has been introduced in version 0.29.\n" ); diff --git a/testdata/ruby/dbEdgesTest.rb b/testdata/ruby/dbEdgesTest.rb index 5d3eb06604..6d6f8df37f 100644 --- a/testdata/ruby/dbEdgesTest.rb +++ b/testdata/ruby/dbEdgesTest.rb @@ -45,6 +45,49 @@ def selected(edge) end +class ShrinkToHalfEdgeOperator < RBA::EdgeOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + # Shrink to half size + def process(edge) + shift = edge.bbox.center - RBA::Point::new # shift vector + return [ (edge.moved(-shift) * 0.5).moved(shift) ] + end + +end + +class SomeEdgeToEdgePairOperator < RBA::EdgeToEdgePairOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(edge) + box = edge.bbox + return [ RBA::EdgePair::new([ box.left, box.bottom, box.left, box.top ], [ box.right, box.bottom, box.right, box.top ]) ] + end + +end + +class SomeEdgeToPolygonOperator < RBA::EdgeToPolygonOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(edge) + box = edge.bbox + return [ RBA::Polygon::new(box) ] + end + +end + class DBEdges_TestClass < TestBase # Basics @@ -796,6 +839,73 @@ def test_generic_filters end + # Generic processors + def test_generic_processors_ee + + # Some basic tests for the processor class + + f = ShrinkToHalfEdgeOperator::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 = true + assert_equal(f.requires_raw_input, true) + assert_equal(f.result_is_merged, false) + f.result_is_merged = true + assert_equal(f.result_is_merged, true) + assert_equal(f.result_must_not_be_merged, false) + f.result_must_not_be_merged = true + assert_equal(f.result_must_not_be_merged, true) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + edges = RBA::Edges::new + + edges.insert(RBA::Edge::new(0, 0, 100, 100)) + edges.insert(RBA::Edge::new(200, 300, 200, 500)) + + assert_equal(edges.processed(ShrinkToHalfEdgeOperator::new).to_s, "(25,25;75,75);(200,350;200,450)") + assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)") + edges.process(ShrinkToHalfEdgeOperator::new) + assert_equal(edges.to_s, "(25,25;75,75);(200,350;200,450)") + + end + + # Generic processors + def test_generic_processors_eep + + p = SomeEdgeToEdgePairOperator::new + + edges = RBA::Edges::new + + edges.insert(RBA::Edge::new(0, 0, 100, 100)) + edges.insert(RBA::Edge::new(200, 300, 200, 500)) + + assert_equal(edges.processed(p).to_s, "(0,0;0,100)/(100,0;100,100);(200,300;200,500)/(200,300;200,500)") + assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)") + + end + + # Generic processors + def test_generic_processors_ep + + p = SomeEdgeToPolygonOperator::new + + edges = RBA::Edges::new + + edges.insert(RBA::Edge::new(0, 0, 100, 100)) + edges.insert(RBA::Edge::new(200, 300, 200, 500)) + + assert_equal(edges.processed(p).to_s, "(0,0;0,100;100,100;100,0);(200,300;200,300;200,500;200,500)") + assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)") + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 87da79aa10..0297e36a00 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -44,7 +44,7 @@ def selected(polygon) end -class ShrinkToHalfProcessor < RBA::PolygonProcessor +class ShrinkToHalfOperator < RBA::PolygonOperator # Constructor def initialize @@ -59,7 +59,7 @@ def process(polygon) end -class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor +class SomePolygonToEdgePairOperator < RBA::PolygonToEdgePairOperator # Constructor def initialize @@ -73,7 +73,7 @@ def process(polygon) end -class SomePolygonToEdgeProcessor < RBA::PolygonToEdgeProcessor +class SomePolygonToEdgeOperator < RBA::PolygonToEdgeOperator # Constructor def initialize @@ -1281,7 +1281,7 @@ def test_generic_processors_pp # Some basic tests for the processor class - f = ShrinkToHalfProcessor::new + f = ShrinkToHalfOperator::new assert_equal(f.wants_variants?, true) f.wants_variants = false assert_equal(f.wants_variants?, false) @@ -1306,9 +1306,9 @@ def test_generic_processors_pp region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]])) region.insert(RBA::Box::new(200, 0, 300, 100)) - assert_equal(region.processed(ShrinkToHalfProcessor::new).to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") + assert_equal(region.processed(ShrinkToHalfOperator::new).to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") - region.process(ShrinkToHalfProcessor::new) + region.process(ShrinkToHalfOperator::new) assert_equal(region.to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") end @@ -1316,7 +1316,7 @@ def test_generic_processors_pp # Generic processors def test_generic_processors_pep - p = SomePolygonToEdgePairProcessor::new + p = SomePolygonToEdgePairOperator::new region = RBA::Region::new @@ -1331,7 +1331,7 @@ def test_generic_processors_pep # Generic processors def test_generic_processors_pe - p = SomePolygonToEdgeProcessor::new + p = SomePolygonToEdgeOperator::new region = RBA::Region::new From ce88affa67b6a49f09f9685d7650fdcfe26e978e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 26 Jan 2024 16:09:01 +0100 Subject: [PATCH 08/11] EdgePairs generic processor --- src/db/db/dbAsIfFlatEdgePairs.cc | 22 +++++ src/db/db/dbAsIfFlatEdgePairs.h | 10 ++- src/db/db/dbDeepEdgePairs.cc | 12 +++ src/db/db/dbDeepEdgePairs.h | 2 + src/db/db/dbEdgePairs.cc | 13 ++- src/db/db/dbEdgePairs.h | 22 ++++- src/db/db/dbEdgePairsDelegate.h | 7 +- src/db/db/dbEmptyEdgePairs.h | 2 + src/db/db/gsiDeclDbEdgePairs.cc | 142 +++++++++++++++++++++++++++++++ src/db/db/gsiDeclDbEdges.cc | 8 +- src/db/db/gsiDeclDbRegion.cc | 8 +- testdata/ruby/dbEdgePairsTest.rb | 95 +++++++++++++++++++++ 12 files changed, 325 insertions(+), 18 deletions(-) diff --git a/src/db/db/dbAsIfFlatEdgePairs.cc b/src/db/db/dbAsIfFlatEdgePairs.cc index cf9c3621b0..d1fb9b0cde 100644 --- a/src/db/db/dbAsIfFlatEdgePairs.cc +++ b/src/db/db/dbAsIfFlatEdgePairs.cc @@ -147,6 +147,28 @@ void AsIfFlatEdgePairs::invalidate_bbox () m_bbox_valid = false; } +EdgePairsDelegate * +AsIfFlatEdgePairs::processed (const EdgePairProcessorBase &filter) const +{ + std::unique_ptr edge_pairs (new FlatEdgePairs ()); + + if (filter.result_must_not_be_merged ()) { + edge_pairs->set_merged_semantics (false); + } + + std::vector res_edge_pairs; + + for (EdgePairsIterator e = begin (); ! e.at_end (); ++e) { + res_edge_pairs.clear (); + filter.process (*e, res_edge_pairs); + for (std::vector::const_iterator er = res_edge_pairs.begin (); er != res_edge_pairs.end (); ++er) { + edge_pairs->insert (*er); + } + } + + return edge_pairs.release (); +} + RegionDelegate * AsIfFlatEdgePairs::processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbAsIfFlatEdgePairs.h b/src/db/db/dbAsIfFlatEdgePairs.h index 29f18f6f14..2a69bb0358 100644 --- a/src/db/db/dbAsIfFlatEdgePairs.h +++ b/src/db/db/dbAsIfFlatEdgePairs.h @@ -53,8 +53,14 @@ class DB_PUBLIC AsIfFlatEdgePairs virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &) const; - virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const; - virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const; + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &proc) + { + return processed (proc); + } + + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &proc) const; + virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &proc) const; + virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &proc) const; virtual EdgePairsDelegate *add_in_place (const EdgePairs &other) { diff --git a/src/db/db/dbDeepEdgePairs.cc b/src/db/db/dbDeepEdgePairs.cc index d19a08fa22..0906442654 100644 --- a/src/db/db/dbDeepEdgePairs.cc +++ b/src/db/db/dbDeepEdgePairs.cc @@ -451,6 +451,18 @@ DeepEdgePairs::apply_filter (const EdgePairFilterBase &filter) const return res.release (); } +EdgePairsDelegate *DeepEdgePairs::process_in_place (const EdgePairProcessorBase &filter) +{ + // TODO: implement to be really in-place + return processed (filter); +} + +EdgePairsDelegate * +DeepEdgePairs::processed (const EdgePairProcessorBase &filter) const +{ + return shape_collection_processed_impl (deep_layer (), filter); +} + RegionDelegate * DeepEdgePairs::processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbDeepEdgePairs.h b/src/db/db/dbDeepEdgePairs.h index ccc73a762f..8ec686da00 100644 --- a/src/db/db/dbDeepEdgePairs.h +++ b/src/db/db/dbDeepEdgePairs.h @@ -78,6 +78,8 @@ class DB_PUBLIC DeepEdgePairs virtual EdgePairsDelegate *filter_in_place (const EdgePairFilterBase &filter); virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &) const; + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &); + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &) const; virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const; virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const; diff --git a/src/db/db/dbEdgePairs.cc b/src/db/db/dbEdgePairs.cc index 0451006ef8..fcfd405f99 100644 --- a/src/db/db/dbEdgePairs.cc +++ b/src/db/db/dbEdgePairs.cc @@ -169,14 +169,19 @@ EdgePairs::properties_repository () return *r; } -void EdgePairs::processed (Region &output, const EdgePairToPolygonProcessorBase &filter) const +EdgePairs EdgePairs::processed (const EdgePairProcessorBase &proc) const { - output = Region (mp_delegate->processed_to_polygons (filter)); + return EdgePairs (mp_delegate->processed (proc)); } -void EdgePairs::processed (Edges &output, const EdgePairToEdgeProcessorBase &filter) const +void EdgePairs::processed (Region &output, const EdgePairToPolygonProcessorBase &proc) const { - output = Edges (mp_delegate->processed_to_edges (filter)); + output = Region (mp_delegate->processed_to_polygons (proc)); +} + +void EdgePairs::processed (Edges &output, const EdgePairToEdgeProcessorBase &proc) const +{ + output = Edges (mp_delegate->processed_to_edges (proc)); } void EdgePairs::polygons (Region &output, db::Coord e) const diff --git a/src/db/db/dbEdgePairs.h b/src/db/db/dbEdgePairs.h index 38e32d499a..9d1b1aee2e 100644 --- a/src/db/db/dbEdgePairs.h +++ b/src/db/db/dbEdgePairs.h @@ -328,13 +328,31 @@ class DB_PUBLIC EdgePairs return EdgePairs (mp_delegate->filtered (filter)); } + /** + * @brief Processes the edge pairs in-place + * + * This method will run the processor over all edge pairs and replace the collection by the results. + */ + EdgePairs &process (const EdgePairProcessorBase &proc) + { + set_delegate (mp_delegate->process_in_place (proc)); + return *this; + } + + /** + * @brief Processes the edge pairs + * + * This method will run the processor over all edge pairs return a new edge pair collection with the results. + */ + EdgePairs processed (const EdgePairProcessorBase &proc) const; + /** * @brief Processes the edge pairs into polygons * * This method will run the processor over all edge pairs and return a region * with the outputs of the processor. */ - void processed (Region &output, const EdgePairToPolygonProcessorBase &filter) const; + void processed (Region &output, const EdgePairToPolygonProcessorBase &proc) const; /** * @brief Processes the edge pairs into edges @@ -342,7 +360,7 @@ class DB_PUBLIC EdgePairs * This method will run the processor over all edge pairs and return a edge collection * with the outputs of the processor. */ - void processed (Edges &output, const EdgePairToEdgeProcessorBase &filter) const; + void processed (Edges &output, const EdgePairToEdgeProcessorBase &proc) const; /** * @brief Transforms the edge pair set diff --git a/src/db/db/dbEdgePairsDelegate.h b/src/db/db/dbEdgePairsDelegate.h index 32fa03670e..189ff293e9 100644 --- a/src/db/db/dbEdgePairsDelegate.h +++ b/src/db/db/dbEdgePairsDelegate.h @@ -39,6 +39,7 @@ class RegionDelegate; class EdgesDelegate; class Layout; +typedef shape_collection_processor EdgePairProcessorBase; typedef shape_collection_processor EdgePairToPolygonProcessorBase; typedef shape_collection_processor EdgePairToEdgeProcessorBase; @@ -194,8 +195,10 @@ class DB_PUBLIC EdgePairsDelegate virtual EdgePairsDelegate *filter_in_place (const EdgePairFilterBase &filter) = 0; virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &filter) const = 0; - virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const = 0; - virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const = 0; + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &proc) = 0; + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &proc) const = 0; + virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &proc) const = 0; + virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &proc) const = 0; virtual RegionDelegate *polygons (db::Coord e) const = 0; virtual EdgesDelegate *edges () const = 0; diff --git a/src/db/db/dbEmptyEdgePairs.h b/src/db/db/dbEmptyEdgePairs.h index 6680ec6a3d..16be5fa99c 100644 --- a/src/db/db/dbEmptyEdgePairs.h +++ b/src/db/db/dbEmptyEdgePairs.h @@ -56,6 +56,8 @@ class DB_PUBLIC EmptyEdgePairs virtual EdgePairsDelegate *filter_in_place (const EdgePairFilterBase &) { return this; } virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &) const { return new EmptyEdgePairs (); } + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &) { return this; } + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &) const { return new EmptyEdgePairs (); } virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const; virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const; diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 3f56397200..6f4b1d60fd 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -109,6 +109,100 @@ Class decl_EdgePairFilterImpl ("db", "EdgePairFilter", "This class has been introduced in version 0.29.\n" ); +// --------------------------------------------------------------------------------- +// EdgePairProcessor binding + +Class > decl_EdgePairProcessor ("db", "EdgePairOperator", + shape_processor_impl::method_decls (false), + "@brief A generic edge-pair operator\n" + "\n" + "Edge pair processors are an efficient way to process edge pairs from an edge pair collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\EdgePairs#processed or \\EdgePairs#process method.\n" + "\n" + "Conceptually, these methods take each edge pair from the edge pair collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edge_pairs derived from the input edge pair.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 flips the edge pairs (swaps first and second edge):" + "\n" + "@code\n" + "class FlipEdgePairs < RBA::EdgePairOperator\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n" + " end\n" + " \n" + " # Flips the edge pair\n" + " def process(edge_pair)\n" + " return [ RBA::EdgePair::new(edge_pair.second, edge_pair.first) ]\n" + " end\n" + "\n" + "end\n" + "\n" + "edge_pairs = ... # some EdgePairs object\n" + "flipped = edge_pairs.processed(FlipEdgePairs::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgePairToPolygonProcessor ("db", "EdgePairToPolygonOperator", + shape_processor_impl::method_decls (false), + "@brief A generic edge-pair-to-polygon operator\n" + "\n" + "Edge pair processors are an efficient way to process edge pairs from an edge pair collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\EdgePairs#processed method.\n" + "\n" + "Conceptually, these methods take each edge pair from the edge pair collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input edge pair.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeToPolygonOperator class, with the exception that this incarnation receives edge pairs.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgePairToEdgeProcessor ("db", "EdgePairToEdgeOperator", + shape_processor_impl::method_decls (false), + "@brief A generic edge-pair-to-edge operator\n" + "\n" + "Edge processors are an efficient way to process edge pairs from an edge pair collection. To apply a processor, derive your own " + "operator class and pass an instance to \\EdgePairs#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edges derived from the input edge pair.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeToEdgePairOperator class, with the exception that this incarnation has to deliver edges and takes edge pairs.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + // --------------------------------------------------------------------------------- // EdgePairs binding @@ -267,6 +361,30 @@ static void filter (db::EdgePairs *r, const EdgePairFilterImpl *f) r->filter (*f); } +static db::EdgePairs processed_epep (const db::EdgePairs *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_epep (db::EdgePairs *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::Edges processed_epe (const db::EdgePairs *r, const shape_processor_impl *f) +{ + db::Edges out; + r->processed (out, *f); + return out; +} + +static db::Region processed_epp (const db::EdgePairs *r, const shape_processor_impl *f) +{ + db::Region out; + r->processed (out, *f); + return out; +} + static db::EdgePairs with_distance1 (const db::EdgePairs *r, db::EdgePairs::distance_type length, bool inverse) { db::EdgePairFilterByDistance ef (length, length + 1, inverse); @@ -717,6 +835,30 @@ Class decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs", "\n" "This method has been introduced in version 0.29.\n" ) + + method_ext ("process", &process_epep, gsi::arg ("process"), + "@brief Applies a generic edge pair processor in place (replacing the edge pairs from the EdgePairs collection)\n" + "See \\EdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_epep, gsi::arg ("processed"), + "@brief Applies a generic edge pair processor and returns a processed copy\n" + "See \\EdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_epe, gsi::arg ("processed"), + "@brief Applies a generic edge-pair-to-edge processor and returns an edge collection with the results\n" + "See \\EdgePairToEdgeProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_epp, gsi::arg ("processed"), + "@brief Applies a generic edge-pair-to-polygon processor and returns an Region with the results\n" + "See \\EdgePairToPolygonProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"), "@brief Filters the edge pairs by length of one of their edges\n" "Filters the edge pairs in the edge pair collection by length of at least one of their edges. If \"inverse\" is false, only " diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 8816442170..ca04d56753 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -84,7 +84,7 @@ Class decl_EdgeFilterImpl ("db", "EdgeFilter", "@brief A generic edge filter adaptor\n" "\n" "Edge filters are an efficient way to filter edge from a Edges collection. To apply a filter, derive your own " - "filter class and pass an instance to \\Edges#filter or \\Edges#filtered method.\n" + "filter class and pass an instance to the \\Edges#filter or \\Edges#filtered method.\n" "\n" "Conceptually, these methods take each edge from the collection and present it to the filter's 'selected' method.\n" "Based on the result of this evaluation, the edge is kept or discarded.\n" @@ -131,7 +131,7 @@ Class > decl_EdgeProcessorBase ("db" "@brief A generic edge-to-polygon operator\n" "\n" "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " - "operator class and pass an instance to \\Edges#processed method.\n" + "operator class and pass an instance to the \\Edges#processed method.\n" "\n" "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" "The result of this call is a list of zero to many output edges derived from the input edge.\n" @@ -176,7 +176,7 @@ Class > decl_EdgeToPolygonP "@brief A generic edge-to-polygon operator\n" "\n" "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " - "operator class and pass an instance to \\Edges#processed method.\n" + "operator class and pass an instance to the \\Edges#processed method.\n" "\n" "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" "The result of this call is a list of zero to many output polygons derived from the input edge.\n" @@ -200,7 +200,7 @@ Class > decl_EdgeToEdgePai "@brief A generic edge-to-edge-pair operator\n" "\n" "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " - "operator class and pass an instance to \\Edges#processed method.\n" + "operator class and pass an instance to the \\Edges#processed method.\n" "\n" "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" "The result of this call is a list of zero to many output edge pairs derived from the input edge.\n" diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index a5d871e39c..fb8346c2d6 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -90,7 +90,7 @@ Class decl_PolygonFilterImpl ("db", "PolygonFilter", "@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" + "filter class and pass an instance to the \\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" @@ -135,7 +135,7 @@ Class > decl_PolygonOperator ("db "@brief A generic polygon operator\n" "\n" "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " - "operator class and pass an instance to \\Region#process or \\Region#processed method.\n" + "operator class and pass an instance to the \\Region#process or \\Region#processed method.\n" "\n" "Conceptually, these methods take each polygon from the region and present it to the operators' 'process' method.\n" "The result of this call is a list of zero to many output polygons derived from the input polygon.\n" @@ -180,7 +180,7 @@ Class > decl_PolygonToEdgeP "@brief A generic polygon-to-edge operator\n" "\n" "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " - "operator class and pass an instance to \\Region#processed method.\n" + "operator class and pass an instance to the \\Region#processed method.\n" "\n" "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n" "The result of this call is a list of zero to many output edges derived from the input polygon.\n" @@ -204,7 +204,7 @@ Class > decl_PolygonToE "@brief A generic polygon-to-edge-pair operator\n" "\n" "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " - "operator class and pass an instance to \\Region#processed method.\n" + "operator class and pass an instance to the \\Region#processed method.\n" "\n" "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n" "The result of this call is a list of zero to many output edge pairs derived from the input polygon.\n" diff --git a/testdata/ruby/dbEdgePairsTest.rb b/testdata/ruby/dbEdgePairsTest.rb index b5db1f8661..f9b3289bec 100644 --- a/testdata/ruby/dbEdgePairsTest.rb +++ b/testdata/ruby/dbEdgePairsTest.rb @@ -44,6 +44,46 @@ def selected(edge_pair) end +class FlipEdgePair < RBA::EdgePairOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + end + + # Flips the edge pair + def process(edge_pair) + return [ RBA::EdgePair::new(edge_pair.second, edge_pair.first) ] + end + +end + +class SomeEdgePairToEdgeOperator < RBA::EdgePairToEdgeOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(ep) + return [ RBA::Edge::new(ep.first.p1, ep.second.p2) ] + end + +end + +class SomeEdgePairToPolygonOperator < RBA::EdgePairToPolygonOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(ep) + return [ RBA::Polygon::new(ep.bbox) ] + end + +end + class DBEdgePairs_TestClass < TestBase # Basics @@ -374,6 +414,61 @@ def test_generic_filters end + # Generic processors + def test_generic_processors_epep + + # Some basic tests for the processor class + + f = FlipEdgePair::new + assert_equal(f.wants_variants?, true) + f.wants_variants = false + assert_equal(f.wants_variants?, false) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + edge_pairs = RBA::EdgePairs::new + + edge_pairs.insert(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 100), RBA::Edge::new(200, 300, 200, 500))) + + assert_equal(edge_pairs.processed(FlipEdgePair::new).to_s, "(200,300;200,500)/(0,0;100,100)") + assert_equal(edge_pairs.to_s, "(0,0;100,100)/(200,300;200,500)") + edge_pairs.process(FlipEdgePair::new) + assert_equal(edge_pairs.to_s, "(200,300;200,500)/(0,0;100,100)") + + end + + # Generic processors + def test_generic_processors_epe + + p = SomeEdgePairToEdgeOperator::new + + edge_pairs = RBA::EdgePairs::new + + edge_pairs.insert(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 100), RBA::Edge::new(200, 300, 200, 500))) + + assert_equal(edge_pairs.processed(p).to_s, "(0,0;200,500)") + assert_equal(edge_pairs.to_s, "(0,0;100,100)/(200,300;200,500)") + + end + + # Generic processors + def test_generic_processors_epp + + p = SomeEdgePairToPolygonOperator::new + + edge_pairs = RBA::EdgePairs::new + + edge_pairs.insert(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 100), RBA::Edge::new(200, 300, 200, 500))) + + assert_equal(edge_pairs.processed(p).to_s, "(0,0;0,500;200,500;200,0)") + assert_equal(edge_pairs.to_s, "(0,0;100,100)/(200,300;200,500)") + + end + end From 8d6125dd748e98ee58a5dd9d47ebce40972e85e4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jan 2024 15:57:01 +0100 Subject: [PATCH 09/11] More processors and tests --- src/db/db/dbAsIfFlatEdgePairs.cc | 4 -- src/db/db/dbAsIfFlatTexts.cc | 18 ++++++ src/db/db/dbAsIfFlatTexts.h | 6 ++ src/db/db/dbDeepTexts.cc | 12 ++++ src/db/db/dbDeepTexts.h | 2 + src/db/db/dbEmptyTexts.h | 2 + src/db/db/dbTexts.cc | 5 ++ src/db/db/dbTexts.h | 20 +++++- src/db/db/dbTextsDelegate.h | 5 +- src/db/db/gsiDeclDbEdgePairs.cc | 2 +- src/db/db/gsiDeclDbText.cc | 14 ++-- src/db/db/gsiDeclDbTexts.cc | 107 +++++++++++++++++++++++++++++++ testdata/ruby/dbTextsTest.rb | 76 +++++++++++++++++++++- 13 files changed, 258 insertions(+), 15 deletions(-) diff --git a/src/db/db/dbAsIfFlatEdgePairs.cc b/src/db/db/dbAsIfFlatEdgePairs.cc index d1fb9b0cde..d0f098e27b 100644 --- a/src/db/db/dbAsIfFlatEdgePairs.cc +++ b/src/db/db/dbAsIfFlatEdgePairs.cc @@ -152,10 +152,6 @@ AsIfFlatEdgePairs::processed (const EdgePairProcessorBase &filter) const { std::unique_ptr edge_pairs (new FlatEdgePairs ()); - if (filter.result_must_not_be_merged ()) { - edge_pairs->set_merged_semantics (false); - } - std::vector res_edge_pairs; for (EdgePairsIterator e = begin (); ! e.at_end (); ++e) { diff --git a/src/db/db/dbAsIfFlatTexts.cc b/src/db/db/dbAsIfFlatTexts.cc index 0e38590a6a..8c73f1c1a1 100644 --- a/src/db/db/dbAsIfFlatTexts.cc +++ b/src/db/db/dbAsIfFlatTexts.cc @@ -164,6 +164,24 @@ AsIfFlatTexts::filtered (const TextFilterBase &filter) const return new_texts.release (); } +TextsDelegate * +AsIfFlatTexts::processed (const TextProcessorBase &filter) const +{ + std::unique_ptr texts (new FlatTexts ()); + + std::vector res_texts; + + for (TextsIterator e = begin (); ! e.at_end (); ++e) { + res_texts.clear (); + filter.process (*e, res_texts); + for (std::vector::const_iterator er = res_texts.begin (); er != res_texts.end (); ++er) { + texts->insert (*er); + } + } + + return texts.release (); +} + RegionDelegate * AsIfFlatTexts::processed_to_polygons (const TextToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbAsIfFlatTexts.h b/src/db/db/dbAsIfFlatTexts.h index ffff931e16..07c023741f 100644 --- a/src/db/db/dbAsIfFlatTexts.h +++ b/src/db/db/dbAsIfFlatTexts.h @@ -55,6 +55,12 @@ class DB_PUBLIC AsIfFlatTexts virtual TextsDelegate *filtered (const TextFilterBase &) const; + virtual TextsDelegate *process_in_place (const TextProcessorBase &proc) + { + return processed (proc); + } + + virtual TextsDelegate *processed (const TextProcessorBase &proc) const; virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const; virtual TextsDelegate *add_in_place (const Texts &other) diff --git a/src/db/db/dbDeepTexts.cc b/src/db/db/dbDeepTexts.cc index f328452eaa..ebf6daccd0 100644 --- a/src/db/db/dbDeepTexts.cc +++ b/src/db/db/dbDeepTexts.cc @@ -474,6 +474,18 @@ DeepTexts *DeepTexts::apply_filter (const TextFilterBase &filter) const return res.release (); } +TextsDelegate *DeepTexts::process_in_place (const TextProcessorBase &filter) +{ + // TODO: implement to be really in-place + return processed (filter); +} + +TextsDelegate * +DeepTexts::processed (const TextProcessorBase &filter) const +{ + return shape_collection_processed_impl (deep_layer (), filter); +} + RegionDelegate * DeepTexts::processed_to_polygons (const TextToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbDeepTexts.h b/src/db/db/dbDeepTexts.h index 1bd5bb66c5..44c367e071 100644 --- a/src/db/db/dbDeepTexts.h +++ b/src/db/db/dbDeepTexts.h @@ -80,6 +80,8 @@ class DB_PUBLIC DeepTexts virtual TextsDelegate *filter_in_place (const TextFilterBase &filter); virtual TextsDelegate *filtered (const TextFilterBase &) const; + virtual TextsDelegate *process_in_place (const TextProcessorBase &); + virtual TextsDelegate *processed (const TextProcessorBase &) const; virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const; virtual TextsDelegate *add_in_place (const Texts &other); diff --git a/src/db/db/dbEmptyTexts.h b/src/db/db/dbEmptyTexts.h index c224dadaa2..9bd206de98 100644 --- a/src/db/db/dbEmptyTexts.h +++ b/src/db/db/dbEmptyTexts.h @@ -57,6 +57,8 @@ class DB_PUBLIC EmptyTexts virtual TextsDelegate *filter_in_place (const TextFilterBase &) { return this; } virtual TextsDelegate *filtered (const TextFilterBase &) const { return new EmptyTexts (); } + virtual TextsDelegate *process_in_place (const TextProcessorBase &) { return this; } + virtual TextsDelegate *processed (const TextProcessorBase &) const { return new EmptyTexts (); } virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &) const; virtual RegionDelegate *polygons (db::Coord e) const; diff --git a/src/db/db/dbTexts.cc b/src/db/db/dbTexts.cc index 31d6ae0a8b..0b90c8897d 100644 --- a/src/db/db/dbTexts.cc +++ b/src/db/db/dbTexts.cc @@ -199,6 +199,11 @@ MutableTexts *Texts::mutable_texts () return texts; } +Texts Texts::processed (const TextProcessorBase &proc) const +{ + return Texts (mp_delegate->processed (proc)); +} + void Texts::processed (Region &output, const TextToPolygonProcessorBase &filter) const { output = Region (mp_delegate->processed_to_polygons (filter)); diff --git a/src/db/db/dbTexts.h b/src/db/db/dbTexts.h index 3ed3cf1f85..82d0171874 100644 --- a/src/db/db/dbTexts.h +++ b/src/db/db/dbTexts.h @@ -316,7 +316,25 @@ class DB_PUBLIC Texts } /** - * @brief Processes the edges into polygons + * @brief Processes the edge pairs in-place + * + * This method will run the processor over all texts and replace the collection by the results. + */ + Texts &process (const TextProcessorBase &proc) + { + set_delegate (mp_delegate->process_in_place (proc)); + return *this; + } + + /** + * @brief Processes the texts + * + * This method will run the processor over all texts and return a new text collection with the results. + */ + Texts processed (const TextProcessorBase &proc) const; + + /** + * @brief Processes the texts into polygons * * This method will run the processor over all edges and return a region * with the outputs of the processor. diff --git a/src/db/db/dbTextsDelegate.h b/src/db/db/dbTextsDelegate.h index 6a818cec58..05f26188e0 100644 --- a/src/db/db/dbTextsDelegate.h +++ b/src/db/db/dbTextsDelegate.h @@ -40,6 +40,7 @@ class RegionDelegate; class EdgesDelegate; class Layout; +typedef shape_collection_processor TextProcessorBase; typedef shape_collection_processor TextToPolygonProcessorBase; typedef db::generic_shape_iterator_delegate_base TextsIteratorDelegate; @@ -94,7 +95,9 @@ class DB_PUBLIC TextsDelegate virtual TextsDelegate *filter_in_place (const TextFilterBase &filter) = 0; virtual TextsDelegate *filtered (const TextFilterBase &filter) const = 0; - virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const = 0; + virtual TextsDelegate *process_in_place (const TextProcessorBase &proc) = 0; + virtual TextsDelegate *processed (const TextProcessorBase &proc) const = 0; + virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &proc) const = 0; virtual RegionDelegate *polygons (db::Coord e) const = 0; virtual EdgesDelegate *edges () const = 0; diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 6f4b1d60fd..e5926b466f 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -120,7 +120,7 @@ Class > decl_EdgePairProcessor ( "operator class and pass an instance to the \\EdgePairs#processed or \\EdgePairs#process method.\n" "\n" "Conceptually, these methods take each edge pair from the edge pair collection and present it to the operator's 'process' method.\n" - "The result of this call is a list of zero to many output edge_pairs derived from the input edge pair.\n" + "The result of this call is a list of zero to many output edge pairs derived from the input edge pair.\n" "The output edge pair collection is the sum over all these individual results.\n" "\n" "The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible " diff --git a/src/db/db/gsiDeclDbText.cc b/src/db/db/gsiDeclDbText.cc index a823cecc83..be37cf36e9 100644 --- a/src/db/db/gsiDeclDbText.cc +++ b/src/db/db/gsiDeclDbText.cc @@ -98,17 +98,17 @@ struct text_defs t->font (db::Font (f)); } - static int get_font (C *t) + static int get_font (const C *t) { return t->font (); } - static point_type get_pos (C *t) + static point_type get_pos (const C *t) { return t->trans () * point_type (); } - static box_type get_bbox (C *t) + static box_type get_bbox (const C *t) { point_type p = get_pos (t); return box_type (p, p); @@ -124,7 +124,7 @@ struct text_defs t->halign (db::HAlign (f)); } - static db::HAlign get_halign (C *t) + static db::HAlign get_halign (const C *t) { return t->halign (); } @@ -139,12 +139,12 @@ struct text_defs t->valign (db::VAlign (f)); } - static db::VAlign get_valign (C *t) + static db::VAlign get_valign (const C *t) { return t->valign (); } - static C moved (C *c, const vector_type &p) + static C moved (const C *c, const vector_type &p) { return c->transformed (simple_trans_type (p)); } @@ -155,7 +155,7 @@ struct text_defs return *c; } - static C moved_xy (C *c, coord_type dx, coord_type dy) + static C moved_xy (const C *c, coord_type dx, coord_type dy) { return c->transformed (simple_trans_type (vector_type (dx, dy))); } diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index d186c2c324..885508c415 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -107,6 +107,78 @@ Class decl_TextFilterImpl ("db", "TextFilter", "This class has been introduced in version 0.29.\n" ); +// --------------------------------------------------------------------------------- +// TextProcessor binding + +Class > decl_TextProcessor ("db", "TextOperator", + shape_processor_impl::method_decls (false), + "@brief A generic text operator\n" + "\n" + "Text processors are an efficient way to process texts from an text collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\Texts#processed or \\Texts#process method.\n" + "\n" + "Conceptually, these methods take each text from the edge pair collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output texts derived from the input text.\n" + "The output text collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode text collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 replaces the text string:" + "\n" + "@code\n" + "class ReplaceTextString < RBA::TextOperator\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n" + " end\n" + " \n" + " # Replaces the string by a number representing the string length\n" + " def process(text)\n" + " new_text = text.dup # need a copy as we cannot modify the text passed\n" + " new_text.string = text.string.size.to_s\n" + " return [ new_text ]\n" + " end\n" + "\n" + "end\n" + "\n" + "texts = ... # some Texts object\n" + "modified = texts.processed(ReplaceTextString::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_TextToPolygonProcessor ("db", "TextToPolygonOperator", + shape_processor_impl::method_decls (false), + "@brief A generic text-to-polygon operator\n" + "\n" + "Text processors are an efficient way to process texts from an text collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\Texts#processed method.\n" + "\n" + "Conceptually, these methods take each text from the text collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input text.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode text collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\TextOperator class, with the exception that this incarnation delivers polygons.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + // --------------------------------------------------------------------------------- // Texts binding @@ -239,6 +311,23 @@ static void filter (db::Texts *r, const TextFilterImpl *f) r->filter (*f); } +static db::Texts processed_tt (const db::Texts *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_tt (db::Texts *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::Region processed_tp (const db::Texts *r, const shape_processor_impl *f) +{ + db::Region out; + r->processed (out, *f); + return out; +} + static db::Texts with_text (const db::Texts *r, const std::string &text, bool inverse) { db::TextStringFilter f (text, inverse); @@ -500,6 +589,24 @@ Class decl_Texts (decl_dbShapeCollection, "db", "Texts", "\n" "This method has been introduced in version 0.29.\n" ) + + method_ext ("process", &process_tt, gsi::arg ("process"), + "@brief Applies a generic text processor in place (replacing the texts from the text collection)\n" + "See \\TextProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_tt, gsi::arg ("processed"), + "@brief Applies a generic text processor and returns a processed copy\n" + "See \\TextProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_tp, gsi::arg ("processed"), + "@brief Applies a generic text-to-polygon processor and returns a region with the results\n" + "See \\TextToPolygonProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("with_text", with_text, gsi::arg ("text"), gsi::arg ("inverse"), "@brief Filter the text by text string\n" "If \"inverse\" is false, this method returns the texts with the given string.\n" diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 385c50a00d..70429a1a0b 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -29,7 +29,7 @@ def csort(s) # splits at ");(" without consuming the brackets s.split(/(?<=\));(?=\()/).sort.join(";") end - + class TextStringLengthFilter < RBA::TextFilter # Constructor @@ -45,6 +45,37 @@ def selected(text) end +class ReplaceTextString < RBA::TextOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + end + + # Replaces the string by a number representing the string length + def process(text) + new_text = text.dup # need a copy as we cannot modify the text passed + new_text.string = text.string.size.to_s + return [ new_text ] + end + +end + +class SomeTextToPolygonOperator < RBA::TextToPolygonOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + end + + # Replaces the string by a number representing the string length + def process(text) + s = text.string.size * 10 + return [ RBA::Polygon::new(text.bbox.enlarged(s)) ] + end + +end + class DBTexts_TestClass < TestBase # Basics @@ -369,6 +400,49 @@ def test_generic_filters end + # Generic processors + def test_generic_processors_tt + + # Some basic tests for the processor class + + f = ReplaceTextString::new + assert_equal(f.wants_variants?, true) + f.wants_variants = false + assert_equal(f.wants_variants?, false) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + texts = RBA::Texts::new + + texts.insert(RBA::Text::new("abc", RBA::Trans::new)) + texts.insert(RBA::Text::new("a long text", RBA::Trans::M45)) + + assert_equal(texts.processed(ReplaceTextString::new).to_s, "('3',r0 0,0);('11',m45 0,0)") + assert_equal(texts.to_s, "('abc',r0 0,0);('a long text',m45 0,0)") + texts.process(ReplaceTextString::new) + assert_equal(texts.to_s, "('3',r0 0,0);('11',m45 0,0)") + + end + + # Generic processors + def test_generic_processors_tp + + p = SomeTextToPolygonOperator::new + + texts = RBA::Texts::new + + texts.insert(RBA::Text::new("abc", RBA::Trans::new)) + texts.insert(RBA::Text::new("a long text", RBA::Trans::M45)) + + assert_equal(texts.processed(p).to_s, "(-30,-30;-30,30;30,30;30,-30);(-110,-110;-110,110;110,110;110,-110)") + assert_equal(texts.to_s, "('abc',r0 0,0);('a long text',m45 0,0)") + + end + end From c1394eadefc20278b94213d8d94f7e08d4469cd4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jan 2024 16:14:53 +0100 Subject: [PATCH 10/11] Disabled assignment and copy for operators and filter objects --- src/db/db/gsiDeclDbContainerHelpers.h | 4 ++++ src/db/db/gsiDeclDbEdgePairs.cc | 5 +++++ src/db/db/gsiDeclDbEdges.cc | 5 +++++ src/db/db/gsiDeclDbRegion.cc | 5 +++++ src/db/db/gsiDeclDbTexts.cc | 5 +++++ 5 files changed, 24 insertions(+) diff --git a/src/db/db/gsiDeclDbContainerHelpers.h b/src/db/db/gsiDeclDbContainerHelpers.h index 278b793376..7c78def79b 100644 --- a/src/db/db/gsiDeclDbContainerHelpers.h +++ b/src/db/db/gsiDeclDbContainerHelpers.h @@ -450,6 +450,10 @@ class shape_processor_impl bool m_wants_variants; bool m_result_is_merged; bool m_result_must_not_be_merged; + + // No copying + shape_processor_impl &operator= (const shape_processor_impl &); + shape_processor_impl (const shape_processor_impl &); }; } diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index e5926b466f..6a569224f1 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -60,6 +60,11 @@ class EdgePairFilterImpl } gsi::Callback f_selected; + +private: + // No copying + EdgePairFilterImpl &operator= (const EdgePairFilterImpl &); + EdgePairFilterImpl (const EdgePairFilterImpl &); }; Class decl_EdgePairFilterImpl ("db", "EdgePairFilter", diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index ca04d56753..8e4c87a09c 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -72,6 +72,11 @@ class EdgeFilterImpl } gsi::Callback f_selected; + +private: + // No copying + EdgeFilterImpl &operator= (const EdgeFilterImpl &); + EdgeFilterImpl (const EdgeFilterImpl &); }; Class decl_EdgeFilterImpl ("db", "EdgeFilter", diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index fb8346c2d6..be8d24b809 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -78,6 +78,11 @@ class PolygonFilterImpl } gsi::Callback f_selected; + +private: + // No copying + PolygonFilterImpl &operator= (const PolygonFilterImpl &); + PolygonFilterImpl (const PolygonFilterImpl &); }; Class decl_PolygonFilterImpl ("db", "PolygonFilter", diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index 885508c415..bdac4d2cb4 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -57,6 +57,11 @@ class TextFilterImpl } gsi::Callback f_selected; + +private: + // No copying + TextFilterImpl &operator= (const TextFilterImpl &); + TextFilterImpl (const TextFilterImpl &); }; Class decl_TextFilterImpl ("db", "TextFilter", From c4fee2cbc4c0a0635a09fc7ff074dec4b831b503 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 28 Jan 2024 23:51:37 +0100 Subject: [PATCH 11/11] Fixed a linker issue --- src/db/db/gsiDeclDbEdgePairs.cc | 2 +- src/db/db/gsiDeclDbEdges.cc | 2 +- src/db/db/gsiDeclDbRegion.cc | 2 +- src/db/db/gsiDeclDbTexts.cc | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 6a569224f1..4a22f7045c 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -55,7 +55,7 @@ class EdgePairFilterImpl if (f_selected.can_issue ()) { return f_selected.issue (&EdgePairFilterImpl::issue_selected, edge_pair); } else { - return db::EdgePairFilterBase::selected (edge_pair); + return issue_selected (edge_pair); } } diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 8e4c87a09c..3ceec3cb88 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -56,7 +56,7 @@ class EdgeFilterImpl if (f_selected.can_issue ()) { return f_selected.issue (&EdgeFilterImpl::issue_selected, edge); } else { - return db::EdgeFilterBase::selected (edge); + return issue_selected (edge); } } diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index be8d24b809..4a0dc7c64c 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -66,7 +66,7 @@ class PolygonFilterImpl if (f_selected.can_issue ()) { return f_selected.issue (&PolygonFilterImpl::issue_selected, polygon); } else { - return db::AllMustMatchFilter::selected (polygon); + return issue_selected (polygon); } } diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index bdac4d2cb4..8be9238b88 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -52,7 +52,7 @@ class TextFilterImpl if (f_selected.can_issue ()) { return f_selected.issue (&TextFilterImpl::issue_selected, text); } else { - return db::TextFilterBase::selected (text); + return issue_selected (text); } }