From b34f539fe1228f092c882e5b31c1e0d850122529 Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Thu, 25 Jan 2024 23:10:12 +0100
Subject: [PATCH 01/63] 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 417351c16..0c3960bb7 100644
--- a/src/db/db/gsiDeclDbPolygon.cc
+++ b/src/db/db/gsiDeclDbPolygon.cc
@@ -61,7 +61,7 @@ struct simple_polygon_defs
}
}
- static point_type point (C *c, size_t p)
+ static point_type point (const C *c, size_t p)
{
if (c->hull ().size () > p) {
return c->hull ()[p];
@@ -70,12 +70,12 @@ struct simple_polygon_defs
}
}
- static size_t num_points (C *c)
+ static size_t num_points (const C *c)
{
return c->hull ().size ();
}
- static bool is_empty (C *c)
+ static bool is_empty (const C *c)
{
return c->hull ().size () == 0;
}
@@ -868,17 +868,17 @@ struct polygon_defs
}
}
- static size_t num_points (C *c)
+ static size_t num_points (const C *c)
{
return c->vertices ();
}
- static bool is_empty (C *c)
+ static bool is_empty (const C *c)
{
return c->vertices () == 0;
}
- static point_type point_hull (C *c, size_t p)
+ static point_type point_hull (const C *c, size_t p)
{
if (c->hull ().size () > p) {
return c->hull ()[p];
@@ -887,7 +887,7 @@ struct polygon_defs
}
}
- static point_type point_hole (C *c, unsigned int n, size_t p)
+ static point_type point_hole (const C *c, unsigned int n, size_t p)
{
if (c->holes () > n && c->contour (n + 1).size () > p) {
return c->contour (n + 1)[p];
@@ -896,12 +896,12 @@ struct polygon_defs
}
}
- static size_t num_points_hull (C *c)
+ static size_t num_points_hull (const C *c)
{
return c->hull ().size ();
}
- static size_t num_points_hole (C *c, unsigned int n)
+ static size_t num_points_hole (const C *c, unsigned int n)
{
return c->contour (n + 1).size ();
}
diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc
index 43b809553..05064472b 100644
--- a/src/db/db/gsiDeclDbRegion.cc
+++ b/src/db/db/gsiDeclDbRegion.cc
@@ -47,6 +47,192 @@
namespace gsi
{
+// ---------------------------------------------------------------------------------
+// PolygonFilter binding
+
+class PolygonFilterImpl
+ : public db::AllMustMatchFilter
+{
+public:
+ PolygonFilterImpl ()
+ {
+ mp_vars = &m_mag_and_orient;
+ m_wants_variants = true;
+ m_requires_raw_input = false;
+ }
+
+ bool issue_selected (const db::Polygon &) const
+ {
+ return false;
+ }
+
+ virtual bool selected (const db::Polygon &polygon) const
+ {
+ if (f_selected.can_issue ()) {
+ return f_selected.issue (&PolygonFilterImpl::issue_selected, polygon);
+ } else {
+ return db::AllMustMatchFilter::selected (polygon);
+ }
+ }
+
+ virtual bool selected (const db::PolygonRef &polygon) const
+ {
+ db::Polygon p;
+ polygon.instantiate (p);
+ return selected (p);
+ }
+
+ virtual const db::TransformationReducer *vars () const
+ {
+ return mp_vars;
+ }
+
+ virtual bool requires_raw_input () const
+ {
+ return m_requires_raw_input;
+ }
+
+ void set_requires_raw_input (bool f)
+ {
+ m_requires_raw_input = f;
+ }
+
+ virtual bool wants_variants () const
+ {
+ return m_wants_variants;
+ }
+
+ void set_wants_variants (bool f)
+ {
+ m_wants_variants = f;
+ }
+
+ void is_isotropic ()
+ {
+ mp_vars = &m_mag;
+ }
+
+ void is_scale_invariant ()
+ {
+ mp_vars = &m_orientation;
+ }
+
+ void is_isotropic_and_scale_invariant ()
+ {
+ mp_vars = 0;
+ }
+
+public:
+ const db::TransformationReducer *mp_vars;
+ db::OrientationReducer m_orientation;
+ db::MagnificationReducer m_mag;
+ db::MagnificationAndOrientationReducer m_mag_and_orient;
+ bool m_requires_raw_input;
+ bool m_wants_variants;
+
+ gsi::Callback f_selected;
+};
+
+Class decl_PluginFactory ("db", "PolygonFilter",
+ method ("requires_raw_input?", &PolygonFilterImpl::requires_raw_input,
+ "@brief Gets a value indicating whether the filter needs raw (unmerged) input\n"
+ "See \\requires_raw_input= for details.\n"
+ ) +
+ method ("requires_raw_input=", &PolygonFilterImpl::set_requires_raw_input, gsi::arg ("flag"),
+ "@brief Sets a value indicating whether the filter needs raw (unmerged) input\n"
+ "This flag must be set before using this filter. It tells the filter implementation whether the "
+ "filter wants to have raw input (unmerged). The default value is 'false', meaning that\n"
+ "the filter will receive merged polygons. Setting this value to false potentially saves some\n"
+ "CPU time needed for merging the polygons.\n"
+ ) +
+ method ("wants_variants?", &PolygonFilterImpl::wants_variants,
+ "@brief Gets a value indicating whether the filter prefers cell variants\n"
+ "See \\wants_variants= for details.\n"
+ ) +
+ method ("wants_variants=", &PolygonFilterImpl::set_wants_variants, gsi::arg ("flag"),
+ "@brief Sets a value indicating whether the filter prefers cell variants\n"
+ "This flag must be set before using this filter. It tells the filter implementation whether cell "
+ "variants should be created (true, the default) or shape propagation will be applied (false).\n"
+ "\n"
+ "This decision needs to be make if the filter indicates that it will deliver different results\n"
+ "for scaled or rotated versions of the cell (see \\is_isotropic and the other hints). If a cell\n"
+ "is present with different respective qualities - as seen from the top cell - these instances\n"
+ "need to be differentiated. Cell variant formation is one way, shape propagation the other way.\n"
+ "Typically, cell variant formation is less expensive, but the hierarchy will be modified internally."
+ ) +
+ method ("is_isotropic", &PolygonFilterImpl::is_isotropic,
+ "@brief Indicates that the filter has isotropic properties\n"
+ "Call this method before using the filter to indicate that the selection is independent of "
+ "the orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
+ "hierarchical mode.\n"
+ "\n"
+ "Examples for isotropic filters are area or perimeter filters."
+ ) +
+ method ("is_scale_invariant", &PolygonFilterImpl::is_scale_invariant,
+ "@brief Indicates that the filter is scale invariant\n"
+ "Call this method before using the filter to indicate that the selection is independent of "
+ "the scale of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
+ "hierarchical mode.\n"
+ "\n"
+ "An example for a scale invariant filter is the bounding box aspect ratio (height/width) filter."
+ ) +
+ method ("is_isotropic_and_scale_invariant", &PolygonFilterImpl::is_isotropic_and_scale_invariant,
+ "@brief Indicates that the filter is isotropic and scale invariant\n"
+ "Call this method before using the filter to indicate that the selection is independent of "
+ "the scale and orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
+ "hierarchical mode.\n"
+ "\n"
+ "An example for such a filter is the rectangle selector."
+ ) +
+ callback ("selected", &PolygonFilterImpl::issue_selected, &PolygonFilterImpl::f_selected, gsi::arg ("polygon"),
+ "@brief Selects a polygon\n"
+ "This method is the actual payload. It needs to be reimplemented in a derived class.\n"
+ "It needs to analyze the polygon and return 'true' if it should be kept and 'false' if it should be discarded."
+ ),
+ "@brief A generic polygon filter adaptor\n"
+ "\n"
+ "Polygon filters are an efficient way to filter polygons from a Region. To apply a filter, derive your own "
+ "filter class and pass an instance to \\Region#filter or \\Region#filtered method.\n"
+ "\n"
+ "Conceptually, these methods take each polygon from the region and present it to the filter's 'selected' method.\n"
+ "Based on the result of this evaluation, the polygon is kept or discarded.\n"
+ "\n"
+ "The magic happens when deep mode regions are involved. In that case, the filter will use as few calls as possible "
+ "and exploit the hierarchical compression if possible. It needs to know however, how the filter behaves. You "
+ "need to configure the filter by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
+ "before using the filter.\n"
+ "\n"
+ "You can skip this step, but the filter algorithm will assume the worst case then. This usually leads to cell variant "
+ "formation which is not always desired and blows up the hierarchy.\n"
+ "\n"
+ "Here is some example that filters triangles:"
+ "\n"
+ "@code\n"
+ "class TriangleFilter < RBA::PolygonFilter\n"
+ "\n"
+ " # Constructor\n"
+ " def initialize\n"
+ " self.is_isotropic_and_scale_invariant # the triangle nature is not dependent on the scale or orientation\n"
+ " end\n"
+ " \n"
+ " # Select only triangles\n"
+ " def selected(polygon)\n"
+ " return polygon.holes == 0 && polygon.num_points == 3\n"
+ " end\n"
+ "\n"
+ "end\n"
+ "\n"
+ "region = ... # some Region\n"
+ "triangles_only = region.filtered(TriangleFilter::new)\n"
+ "@/code\n"
+ "\n"
+ "This class has been introduced in version 0.29.\n"
+);
+
+
+// ---------------------------------------------------------------------------------
+// Region binding
+
static inline std::vector as_2region_vector (const std::pair &rp)
{
std::vector res;
@@ -318,6 +504,16 @@ static db::Edges extent_refs_edges (const db::Region *r, double fx1, double fy1,
return r->processed (db::RelativeExtentsAsEdges (fx1, fy1, fx2, fy2));
}
+static db::Region filtered (const db::Region *r, const PolygonFilterImpl *f)
+{
+ return r->filtered (*f);
+}
+
+static void filter (db::Region *r, const PolygonFilterImpl *f)
+{
+ r->filter (*f);
+}
+
static db::Region with_perimeter1 (const db::Region *r, db::Region::perimeter_type perimeter, bool inverse)
{
db::RegionPerimeterFilter f (perimeter, perimeter + 1, inverse);
@@ -2321,6 +2517,18 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method has been introduced in version 0.28.\n"
) +
+ method_ext ("filter", &filter, gsi::arg ("filter"),
+ "@brief Applies a generic filter in place (replacing the polygons from the Region)\n"
+ "See \\PolygonFilter for a description of this feature.\n"
+ "\n"
+ "This method has been introduced in version 0.29.\n"
+ ) +
+ method_ext ("filtered", &filtered, gsi::arg ("filtered"),
+ "@brief Applies a generic filter and returns a filtered copy\n"
+ "See \\PolygonFilter for a description of this feature.\n"
+ "\n"
+ "This method has been introduced in version 0.29.\n"
+ ) +
method_ext ("rectangles", &rectangles,
"@brief Returns all polygons which are rectangles\n"
"This method returns all polygons in self which are rectangles."
diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb
index de204bc3d..2f33b63df 100644
--- a/testdata/ruby/dbRegionTest.rb
+++ b/testdata/ruby/dbRegionTest.rb
@@ -30,6 +30,20 @@ def csort(s)
s.split(/(?<=\));(?=\()/).sort.join(";")
end
+class TriangleFilter < RBA::PolygonFilter
+
+ # Constructor
+ def initialize
+ self.is_isotropic_and_scale_invariant # the triangle nature is not dependent on the scale or orientation
+ end
+
+ # Select only triangles
+ def selected(polygon)
+ return polygon.holes == 0 && polygon.num_points == 3
+ end
+
+end
+
class DBRegion_TestClass < TestBase
# Basics
@@ -1188,6 +1202,34 @@ class DBRegion_TestClass < TestBase
end
+ # Generic filters
+ def test_generic_filters
+
+ # Some basic tests for the filter class
+
+ f = TriangleFilter::new
+ assert_equal(f.wants_variants?, true)
+ f.wants_variants = false
+ assert_equal(f.wants_variants?, false)
+ assert_equal(f.requires_raw_input, false)
+ f.requires_raw_input = false
+ assert_equal(f.requires_raw_input, false)
+
+ # Smoke test
+ f.is_isotropic
+ f.is_scale_invariant
+
+ # Some application
+
+ region = RBA::Region::new
+
+ region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]]))
+ region.insert(RBA::Box::new(200, 0, 300, 100))
+
+ assert_equal(region.filtered(TriangleFilter::new).to_s, "(0,0;100,100;100,0)")
+
+ end
+
end
load("test_epilogue.rb")
From 0b77ef996b0cdbeea882fbaeb67b1ab0035c42d3 Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Thu, 25 Jan 2024 23:27:39 +0100
Subject: [PATCH 02/63] 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 1bef5efb1..229b2e696 100644
--- a/src/db/db/dbDeepRegion.h
+++ b/src/db/db/dbDeepRegion.h
@@ -182,8 +182,6 @@ private:
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 ab8597646..b1c32aad0 100644
--- a/src/db/db/dbRegionDelegate.h
+++ b/src/db/db/dbRegionDelegate.h
@@ -106,62 +106,6 @@ public:
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/63] 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 05064472b..b42f8874b 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 @@ public:
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 @@ public:
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/63] 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 e16e91be8..ee6112948 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 870991c4d..3f5639720 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 718b6d16b..8640749e4 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 b42f8874b..b6c00648c 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 @@ public:
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 b105c5d82..d186c2c32 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 fa1ccad3c..b5db1f866 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 @@ class DBEdgePairs_TestClass < TestBase
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 1a0910a9d..5d3eb0660 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 @@ class DBEdges_TestClass < TestBase
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 2f33b63df..e0f2f07c9 100644
--- a/testdata/ruby/dbRegionTest.rb
+++ b/testdata/ruby/dbRegionTest.rb
@@ -1227,6 +1227,9 @@ class DBRegion_TestClass < TestBase
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 7b9a65288..385c50a00 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 @@ class DBTexts_TestClass < TestBase
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/63] 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 0d2a448f9..c300bb052 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 ee6112948..278b79337 100644
--- a/src/db/db/gsiDeclDbContainerHelpers.h
+++ b/src/db/db/gsiDeclDbContainerHelpers.h
@@ -238,6 +238,220 @@ private:
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 b6c00648c..83a9b5a62 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 e0f2f07c9..9eea0ff38 100644
--- a/testdata/ruby/dbRegionTest.rb
+++ b/testdata/ruby/dbRegionTest.rb
@@ -44,6 +44,21 @@ class TriangleFilter < RBA::PolygonFilter
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 @@ class DBRegion_TestClass < TestBase
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 @@ class DBRegion_TestClass < TestBase
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/63] 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 9eea0ff38..87da79aa1 100644
--- a/testdata/ruby/dbRegionTest.rb
+++ b/testdata/ruby/dbRegionTest.rb
@@ -59,6 +59,34 @@ class ShrinkToHalfProcessor < RBA::PolygonProcessor
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 @@ class DBRegion_TestClass < TestBase
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 @@ class DBRegion_TestClass < TestBase
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/63] 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 8640749e4..881644217 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 83a9b5a62..a5d871e39 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 5d3eb0660..6d6f8df37 100644
--- a/testdata/ruby/dbEdgesTest.rb
+++ b/testdata/ruby/dbEdgesTest.rb
@@ -45,6 +45,49 @@ class ParallelFilter < RBA::EdgeFilter
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 @@ class DBEdges_TestClass < TestBase
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 87da79aa1..0297e36a0 100644
--- a/testdata/ruby/dbRegionTest.rb
+++ b/testdata/ruby/dbRegionTest.rb
@@ -44,7 +44,7 @@ class TriangleFilter < RBA::PolygonFilter
end
-class ShrinkToHalfProcessor < RBA::PolygonProcessor
+class ShrinkToHalfOperator < RBA::PolygonOperator
# Constructor
def initialize
@@ -59,7 +59,7 @@ class ShrinkToHalfProcessor < RBA::PolygonProcessor
end
-class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor
+class SomePolygonToEdgePairOperator < RBA::PolygonToEdgePairOperator
# Constructor
def initialize
@@ -73,7 +73,7 @@ class SomePolygonToEdgePairProcessor < RBA::PolygonToEdgePairProcessor
end
-class SomePolygonToEdgeProcessor < RBA::PolygonToEdgeProcessor
+class SomePolygonToEdgeOperator < RBA::PolygonToEdgeOperator
# Constructor
def initialize
@@ -1281,7 +1281,7 @@ class DBRegion_TestClass < TestBase
# 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 @@ class DBRegion_TestClass < TestBase
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 @@ class DBRegion_TestClass < TestBase
# Generic processors
def test_generic_processors_pep
- p = SomePolygonToEdgePairProcessor::new
+ p = SomePolygonToEdgePairOperator::new
region = RBA::Region::new
@@ -1331,7 +1331,7 @@ class DBRegion_TestClass < TestBase
# 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/63] 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 cf9c3621b..d1fb9b0cd 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 29f18f6f1..2a69bb035 100644
--- a/src/db/db/dbAsIfFlatEdgePairs.h
+++ b/src/db/db/dbAsIfFlatEdgePairs.h
@@ -53,8 +53,14 @@ public:
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 d19a08fa2..090644265 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 ccc73a762..8ec686da0 100644
--- a/src/db/db/dbDeepEdgePairs.h
+++ b/src/db/db/dbDeepEdgePairs.h
@@ -78,6 +78,8 @@ public:
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 0451006ef..fcfd405f9 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 38e32d499..9d1b1aee2 100644
--- a/src/db/db/dbEdgePairs.h
+++ b/src/db/db/dbEdgePairs.h
@@ -328,13 +328,31 @@ public:
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 @@ public:
* 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 32fa03670..189ff293e 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 @@ public:
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 6680ec6a3..16be5fa99 100644
--- a/src/db/db/dbEmptyEdgePairs.h
+++ b/src/db/db/dbEmptyEdgePairs.h
@@ -56,6 +56,8 @@ public:
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 3f5639720..6f4b1d60f 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 881644217..ca04d5675 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 a5d871e39..fb8346c2d 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 b5db1f866..f9b3289be 100644
--- a/testdata/ruby/dbEdgePairsTest.rb
+++ b/testdata/ruby/dbEdgePairsTest.rb
@@ -44,6 +44,46 @@ class PerpendicularEdgesFilter < RBA::EdgePairFilter
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 @@ class DBEdgePairs_TestClass < TestBase
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/63] 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 d1fb9b0cd..d0f098e27 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 0e38590a6..8c73f1c1a 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 ffff931e1..07c023741 100644
--- a/src/db/db/dbAsIfFlatTexts.h
+++ b/src/db/db/dbAsIfFlatTexts.h
@@ -55,6 +55,12 @@ public:
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 f328452ea..ebf6daccd 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 1bd5bb66c..44c367e07 100644
--- a/src/db/db/dbDeepTexts.h
+++ b/src/db/db/dbDeepTexts.h
@@ -80,6 +80,8 @@ public:
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 c224dadaa..9bd206de9 100644
--- a/src/db/db/dbEmptyTexts.h
+++ b/src/db/db/dbEmptyTexts.h
@@ -57,6 +57,8 @@ public:
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 31d6ae0a8..0b90c8897 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 3ed3cf1f8..82d017187 100644
--- a/src/db/db/dbTexts.h
+++ b/src/db/db/dbTexts.h
@@ -316,7 +316,25 @@ public:
}
/**
- * @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 6a818cec5..05f26188e 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 @@ public:
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 6f4b1d60f..e5926b466 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 a823cecc8..be37cf36e 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 d186c2c32..885508c41 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 385c50a00..70429a1a0 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 @@ class TextStringLengthFilter < RBA::TextFilter
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 @@ class DBTexts_TestClass < TestBase
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/63] 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 278b79337..7c78def79 100644
--- a/src/db/db/gsiDeclDbContainerHelpers.h
+++ b/src/db/db/gsiDeclDbContainerHelpers.h
@@ -450,6 +450,10 @@ private:
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 e5926b466..6a569224f 100644
--- a/src/db/db/gsiDeclDbEdgePairs.cc
+++ b/src/db/db/gsiDeclDbEdgePairs.cc
@@ -60,6 +60,11 @@ public:
}
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 ca04d5675..8e4c87a09 100644
--- a/src/db/db/gsiDeclDbEdges.cc
+++ b/src/db/db/gsiDeclDbEdges.cc
@@ -72,6 +72,11 @@ public:
}
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 fb8346c2d..be8d24b80 100644
--- a/src/db/db/gsiDeclDbRegion.cc
+++ b/src/db/db/gsiDeclDbRegion.cc
@@ -78,6 +78,11 @@ public:
}
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 885508c41..bdac4d2cb 100644
--- a/src/db/db/gsiDeclDbTexts.cc
+++ b/src/db/db/gsiDeclDbTexts.cc
@@ -57,6 +57,11 @@ public:
}
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/63] 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 6a569224f..4a22f7045 100644
--- a/src/db/db/gsiDeclDbEdgePairs.cc
+++ b/src/db/db/gsiDeclDbEdgePairs.cc
@@ -55,7 +55,7 @@ public:
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 8e4c87a09..3ceec3cb8 100644
--- a/src/db/db/gsiDeclDbEdges.cc
+++ b/src/db/db/gsiDeclDbEdges.cc
@@ -56,7 +56,7 @@ public:
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 be8d24b80..4a0dc7c64 100644
--- a/src/db/db/gsiDeclDbRegion.cc
+++ b/src/db/db/gsiDeclDbRegion.cc
@@ -66,7 +66,7 @@ public:
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 bdac4d2cb..8be9238b8 100644
--- a/src/db/db/gsiDeclDbTexts.cc
+++ b/src/db/db/gsiDeclDbTexts.cc
@@ -52,7 +52,7 @@ public:
if (f_selected.can_issue ()) {
return f_selected.issue (&TextFilterImpl::issue_selected, text);
} else {
- return db::TextFilterBase::selected (text);
+ return issue_selected (text);
}
}
From f7411b52d2956fe1b0851534fe2daf03804f2fbe Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Wed, 21 Feb 2024 22:17:24 +0100
Subject: [PATCH 12/63] Fixed a typo in DRC doc of 'corners'
---
src/doc/doc/about/drc_ref.xml | 2 +-
src/doc/doc/about/drc_ref_drc.xml | 14 ++++++++------
src/doc/doc/about/drc_ref_global.xml | 7 +------
src/doc/doc/about/drc_ref_layer.xml | 7 ++-----
src/doc/doc/about/drc_ref_netter.xml | 2 +-
src/doc/doc/about/drc_ref_source.xml | 2 +-
src/doc/doc/about/lvs_ref.xml | 2 +-
src/doc/doc/about/lvs_ref_global.xml | 2 +-
src/doc/doc/about/lvs_ref_netter.xml | 2 +-
src/drc/drc/built-in-macros/_drc_complex_ops.rb | 12 +++++++-----
src/drc/drc/built-in-macros/_drc_layer.rb | 2 +-
11 files changed, 25 insertions(+), 29 deletions(-)
diff --git a/src/doc/doc/about/drc_ref.xml b/src/doc/doc/about/drc_ref.xml
index 889c3fedc..203941a1d 100644
--- a/src/doc/doc/about/drc_ref.xml
+++ b/src/doc/doc/about/drc_ref.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/doc/doc/about/drc_ref_drc.xml b/src/doc/doc/about/drc_ref_drc.xml
index 7ee678eca..7f121c94a 100644
--- a/src/doc/doc/about/drc_ref_drc.xml
+++ b/src/doc/doc/about/drc_ref_drc.xml
@@ -1,7 +1,7 @@
-
+
@@ -358,7 +358,7 @@ The plain function is equivalent to "primary.bbox_width".
This method acts on edge expressions and delivers a specific part of each edge.
See layer#centers for details about this functionality.
-"corners" - Applies smoothing
+"corners" - Selects corners of polygons
Usage:
@@ -369,8 +369,10 @@ See layer#centers for details abo
This operation acts on polygons and selects the corners of the polygons.
It can be put into a condition to select corners by their angles. The angle of
-a corner is positive for a turn to the left if walking a polygon counterclockwise
-and negative for the turn to the right. Angles take values between -180 and 180 degree.
+a corner is positive for a turn to the left if walking a polygon clockwise
+and negative for the turn to the right. Hence positive angles indicate concave
+(inner) corners, negative ones indicate convex (outer) corners.
+Angles take values between -180 and 180 degree.
When using "as_dots" for the argument, the operation will return single-point edges at
the selected corners. With "as_boxes" (the default), small (2x2 DBU) rectangles will be
@@ -386,8 +388,8 @@ out = in.drc(primary.corners) # equivalent
The following example selects all inner corners:
-out = in.drc(corners < 0)
-out = in.drc(primary.corners < 0) # equivalent
+out = in.drc(corners > 0)
+out = in.drc(primary.corners > 0) # equivalent
The "corners" method is available as a plain function or as a method on DRC expressions.
diff --git a/src/doc/doc/about/drc_ref_global.xml b/src/doc/doc/about/drc_ref_global.xml
index 7e8b6ba82..670651628 100644
--- a/src/doc/doc/about/drc_ref_global.xml
+++ b/src/doc/doc/about/drc_ref_global.xml
@@ -1,17 +1,12 @@
-
+
DRC Reference: Global Functions
-
-Some functions are available on global level and can be used without any object.
-Most of them are convenience functions that basically act on some default object
-or provide function-like alternatives for the methods.
-
"angle" - In universal DRC context: selects edges based on their orientation
diff --git a/src/doc/doc/about/drc_ref_layer.xml b/src/doc/doc/about/drc_ref_layer.xml
index ce8c3aa4a..35af6a5b4 100644
--- a/src/doc/doc/about/drc_ref_layer.xml
+++ b/src/doc/doc/about/drc_ref_layer.xml
@@ -1,15 +1,12 @@
-
+
DRC Reference: Layer Object
-
-The layer object represents a collection of polygons, edges or edge pairs.
-
"&" - Boolean AND operation
@@ -268,7 +265,7 @@ deliver objects that can be converted into polygons. Such objects are of class <
This method produces markers on the corners of the polygons. An angle criterion can be given which
selects corners based on the angle of the connecting edges. Positive angles indicate a left turn
while negative angles indicate a right turn. Since polygons are oriented clockwise, positive angles
-indicate concave corners while negative ones indicate convex corners.
+indicate concave (inner) corners while negative ones indicate convex (outer) corners
The markers generated can be point-like edges or small 2x2 DBU boxes. The latter is the default.
diff --git a/src/doc/doc/about/drc_ref_netter.xml b/src/doc/doc/about/drc_ref_netter.xml
index f03149944..8fd41f6f5 100644
--- a/src/doc/doc/about/drc_ref_netter.xml
+++ b/src/doc/doc/about/drc_ref_netter.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/doc/doc/about/drc_ref_source.xml b/src/doc/doc/about/drc_ref_source.xml
index a9ab3eaab..74d119bcb 100644
--- a/src/doc/doc/about/drc_ref_source.xml
+++ b/src/doc/doc/about/drc_ref_source.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/doc/doc/about/lvs_ref.xml b/src/doc/doc/about/lvs_ref.xml
index 4a38dd6db..58168f012 100644
--- a/src/doc/doc/about/lvs_ref.xml
+++ b/src/doc/doc/about/lvs_ref.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/doc/doc/about/lvs_ref_global.xml b/src/doc/doc/about/lvs_ref_global.xml
index c262d6815..c1d2ad914 100644
--- a/src/doc/doc/about/lvs_ref_global.xml
+++ b/src/doc/doc/about/lvs_ref_global.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/doc/doc/about/lvs_ref_netter.xml b/src/doc/doc/about/lvs_ref_netter.xml
index f5f851203..9feef786f 100644
--- a/src/doc/doc/about/lvs_ref_netter.xml
+++ b/src/doc/doc/about/lvs_ref_netter.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/drc/drc/built-in-macros/_drc_complex_ops.rb b/src/drc/drc/built-in-macros/_drc_complex_ops.rb
index c3674e3d2..aa9e9e7d3 100644
--- a/src/drc/drc/built-in-macros/_drc_complex_ops.rb
+++ b/src/drc/drc/built-in-macros/_drc_complex_ops.rb
@@ -756,15 +756,17 @@ CODE
# %DRC%
# @name corners
- # @brief Applies smoothing
+ # @brief Selects corners of polygons
# @synopsis expression.corners
# @synopsis expression.corners(as_dots)
# @synopsis expression.corners(as_boxes)
#
# This operation acts on polygons and selects the corners of the polygons.
# It can be put into a condition to select corners by their angles. The angle of
- # a corner is positive for a turn to the left if walking a polygon counterclockwise
- # and negative for the turn to the right. Angles take values between -180 and 180 degree.
+ # a corner is positive for a turn to the left if walking a polygon clockwise
+ # and negative for the turn to the right. Hence positive angles indicate concave
+ # (inner) corners, negative ones indicate convex (outer) corners.
+ # Angles take values between -180 and 180 degree.
#
# When using "as_dots" for the argument, the operation will return single-point edges at
# the selected corners. With "as_boxes" (the default), small (2x2 DBU) rectangles will be
@@ -780,8 +782,8 @@ CODE
# The following example selects all inner corners:
#
# @code
- # out = in.drc(corners < 0)
- # out = in.drc(primary.corners < 0) # equivalent
+ # out = in.drc(corners > 0)
+ # out = in.drc(primary.corners > 0) # equivalent
# @/code
#
# The "corners" method is available as a plain function or as a method on \DRC# expressions.
diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb
index ffa80b943..24c2cbb2e 100644
--- a/src/drc/drc/built-in-macros/_drc_layer.rb
+++ b/src/drc/drc/built-in-macros/_drc_layer.rb
@@ -1232,7 +1232,7 @@ CODE
# This method produces markers on the corners of the polygons. An angle criterion can be given which
# selects corners based on the angle of the connecting edges. Positive angles indicate a left turn
# while negative angles indicate a right turn. Since polygons are oriented clockwise, positive angles
- # indicate concave corners while negative ones indicate convex corners.
+ # indicate concave (inner) corners while negative ones indicate convex (outer) corners
#
# The markers generated can be point-like edges or small 2x2 DBU boxes. The latter is the default.
#
From 012447c31bfcd6a1d45543c32bfd229aa87de45d Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Wed, 28 Feb 2024 22:41:09 +0100
Subject: [PATCH 13/63] Fixed a small flaw: pipe stream did not report source
in errors
---
src/tl/tl/tlStream.cc | 6 +++---
src/tl/tl/tlStream.h | 5 ++---
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc
index dc57b3a98..dc1425537 100644
--- a/src/tl/tl/tlStream.cc
+++ b/src/tl/tl/tlStream.cc
@@ -1334,11 +1334,11 @@ OutputPipe::write (const char *b, size_t n)
// ---------------------------------------------------------------
// InputPipe delegate implementation
-InputPipe::InputPipe (const std::string &path)
+InputPipe::InputPipe (const std::string &source)
: m_file (NULL)
{
- m_source = path;
- m_file = popen (tl::string_to_system (path).c_str (), "r");
+ m_source = source;
+ m_file = popen (tl::string_to_system (source).c_str (), "r");
if (m_file == NULL) {
throw FilePOpenErrorException (m_source, errno);
}
diff --git a/src/tl/tl/tlStream.h b/src/tl/tl/tlStream.h
index 2412c4663..087e54f45 100644
--- a/src/tl/tl/tlStream.h
+++ b/src/tl/tl/tlStream.h
@@ -313,7 +313,7 @@ public:
* @param cmd The command to execute
* @param read True, if the file should be read, false on write.
*/
- InputPipe (const std::string &path);
+ InputPipe (const std::string &source);
/**
* @brief Close the pipe
@@ -348,8 +348,7 @@ public:
*/
virtual std::string source () const
{
- // No source (in the sense of a file name) is available ..
- return std::string ();
+ return m_source;
}
virtual std::string absolute_path () const
From 9548f5109a608cc176dc0a022b072d8d9c1b38f5 Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Thu, 29 Feb 2024 00:30:34 +0100
Subject: [PATCH 14/63] Implementing automatic .gz support for https and pipe
URIs
---
src/tl/tl/tlHttpStream.h | 16 ++-
src/tl/tl/tlStream.cc | 146 ++++++++++++++++++++++++-
src/tl/tl/tlStream.h | 83 +++++++++++++-
src/tl/unit_tests/tlHttpStreamTests.cc | 28 +++++
src/tl/unit_tests/tlStreamTests.cc | 9 ++
5 files changed, 272 insertions(+), 10 deletions(-)
diff --git a/src/tl/tl/tlHttpStream.h b/src/tl/tl/tlHttpStream.h
index a85dc3348..529e81d85 100644
--- a/src/tl/tl/tlHttpStream.h
+++ b/src/tl/tl/tlHttpStream.h
@@ -112,7 +112,6 @@ public:
* @brief Polling: call this function regularly to explicitly establish polling
* (in the Qt framework, this is done automatically within the event loop)
* May throw a tl::CancelException to stop.
- * Returns true if a message has arrived.
*/
void tick ();
@@ -209,6 +208,21 @@ private:
InputHttpStreamCallback *mp_callback;
};
+/**
+ * @brief A HTTP stream with .gz support
+ */
+class TL_PUBLIC InflatingInputHttpStream
+ : public inflating_input_stream
+{
+public:
+ /**
+ * @brief Open a stream with the given URL
+ */
+ InflatingInputHttpStream (const std::string &url)
+ : inflating_input_stream (new InputHttpStream (url))
+ { }
+};
+
}
#endif
diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc
index dc1425537..4ffc2d43e 100644
--- a/src/tl/tl/tlStream.cc
+++ b/src/tl/tl/tlStream.cc
@@ -140,6 +140,125 @@ public:
gzFile zs;
};
+// ---------------------------------------------------------------
+// inflating_input_stream implementation
+
+/**
+ * @brief A wrapper that adds generic .gz support
+ */
+template
+inflating_input_stream::inflating_input_stream (Base *delegate)
+ : m_inflating_stream (delegate), m_is_compressed (false), mp_delegate (delegate)
+{
+ enter_inflate ();
+}
+
+template
+size_t
+inflating_input_stream::read (char *b, size_t n)
+{
+ // TODO: this is somewhat inefficient, but we only use it for pipe and HTTP streams
+ size_t i = 0;
+ while (i < n) {
+
+ if (m_is_compressed || m_inflating_stream.blen () == 0) {
+
+ const char *read = m_inflating_stream.get (1);
+ if (! read) {
+ break;
+ }
+ *b++ = *read;
+ i += 1;
+
+ } else {
+
+ size_t nn = std::min (n - i, m_inflating_stream.blen ());
+ const char *read = m_inflating_stream.get (nn);
+ tl_assert (read != 0);
+ memcpy (b, read, nn);
+ b += nn;
+ i += nn;
+
+ }
+
+ }
+ return i;
+}
+
+template
+void
+inflating_input_stream::enter_inflate ()
+{
+ // identify and skip header for .gz file
+ if (auto_detect_gz ()) {
+ m_is_compressed = true;
+ m_inflating_stream.inflate (true /* stop after inflated block */);
+ } else {
+ m_inflating_stream.unget (m_inflating_stream.pos ());
+ }
+}
+
+template
+bool
+inflating_input_stream::auto_detect_gz ()
+{
+ std::string header = m_inflating_stream.read_all (10);
+ if (header.size () < 10) {
+ return false;
+ }
+
+ const unsigned char *header_data = (const unsigned char *) header.c_str ();
+ unsigned char flags = header_data[3];
+ if (header_data[0] != 0x1f || header_data[1] != 0x8b || header_data[2] != 0x08 || (flags & 0xe0) != 0) {
+ return false;
+ }
+
+ // .gz signature found
+
+ bool has_fhcrc = (flags & 0x02) != 0;
+ bool has_extra = (flags & 0x04) != 0;
+ bool has_fname = (flags & 0x08) != 0;
+ bool has_comment = (flags & 0x10) != 0;
+
+ if (has_extra) {
+ const unsigned char *xlen = (const unsigned char *) m_inflating_stream.get (2);
+ if (! xlen) {
+ throw tl::Exception (tl::to_string (tr ("Corrupt .gz header - missing XLEN field")));
+ }
+ const char *xdata = m_inflating_stream.get (size_t (xlen[0]) + (size_t (xlen[1]) << 8));
+ if (! xdata) {
+ throw tl::Exception (tl::to_string (tr ("Corrupt .gz header - missing EXTRA data")));
+ }
+ }
+
+ if (has_fname) {
+ const char *c;
+ while ((c = m_inflating_stream.get (1)) != 0 && *c)
+ ;
+ if (! c) {
+ throw tl::Exception (tl::to_string (tr ("Corrupt .gz header - missing FNAME data trailing zero byte")));
+ }
+ }
+
+ if (has_comment) {
+ const char *c;
+ while ((c = m_inflating_stream.get (1)) != 0 && *c)
+ ;
+ if (! c) {
+ throw tl::Exception (tl::to_string (tr ("Corrupt .gz header - missing COMMENT data trailing zero byte")));
+ }
+ }
+
+ if (has_fhcrc) {
+ const char *crc16 = m_inflating_stream.get (2);
+ if (! crc16) {
+ throw tl::Exception (tl::to_string (tr ("Corrupt .gz header - missing CRC16 data")));
+ }
+ }
+
+ return true;
+}
+
// ---------------------------------------------------------------
// InputStream implementation
@@ -175,7 +294,7 @@ public:
}
InputStream::InputStream (InputStreamBase &delegate)
- : m_pos (0), mp_bptr (0), mp_delegate (&delegate), m_owns_delegate (false), mp_inflate (0), m_inflate_always (false)
+ : m_pos (0), mp_bptr (0), mp_delegate (&delegate), m_owns_delegate (false), mp_inflate (0), m_inflate_always (false), m_stop_after_inflate (false)
{
m_bcap = 4096; // initial buffer capacity
m_blen = 0;
@@ -183,7 +302,7 @@ InputStream::InputStream (InputStreamBase &delegate)
}
InputStream::InputStream (InputStreamBase *delegate)
- : m_pos (0), mp_bptr (0), mp_delegate (delegate), m_owns_delegate (true), mp_inflate (0), m_inflate_always (false)
+ : m_pos (0), mp_bptr (0), mp_delegate (delegate), m_owns_delegate (true), mp_inflate (0), m_inflate_always (false), m_stop_after_inflate (false)
{
m_bcap = 4096; // initial buffer capacity
m_blen = 0;
@@ -191,7 +310,7 @@ InputStream::InputStream (InputStreamBase *delegate)
}
InputStream::InputStream (const std::string &abstract_path)
- : m_pos (0), mp_bptr (0), mp_delegate (0), m_owns_delegate (false), mp_inflate (0), m_inflate_always (false)
+ : m_pos (0), mp_bptr (0), mp_delegate (0), m_owns_delegate (false), mp_inflate (0), m_inflate_always (false), m_stop_after_inflate (false)
{
m_bcap = 4096; // initial buffer capacity
m_blen = 0;
@@ -252,7 +371,7 @@ InputStream::InputStream (const std::string &abstract_path)
} else if (ex.test ("pipe:")) {
- mp_delegate = new InputPipe (ex.get ());
+ mp_delegate = new InflatingInputPipe (ex.get ());
} else {
@@ -260,7 +379,7 @@ InputStream::InputStream (const std::string &abstract_path)
if (uri.scheme () == "http" || uri.scheme () == "https") {
#if defined(HAVE_CURL) || defined(HAVE_QT)
- mp_delegate = new InputHttpStream (abstract_path);
+ mp_delegate = new InflatingInputHttpStream (abstract_path);
#else
throw tl::Exception (tl::to_string (tr ("HTTP support not enabled - HTTP/HTTPS paths are not available")));
#endif
@@ -337,9 +456,16 @@ InputStream::get (size_t n, bool bypass_inflate)
tl_assert (r != 0); // since deflate did not report at_end()
return r;
+ } else if (m_stop_after_inflate) {
+
+ // report EOF after the inflator has finished
+ return 0;
+
} else {
+
delete mp_inflate;
mp_inflate = 0;
+
}
}
@@ -384,9 +510,16 @@ InputStream::get (size_t n, bool bypass_inflate)
void
InputStream::unget (size_t n)
{
+ if (n == 0) {
+ return;
+ }
+
if (mp_inflate) {
+ // TODO: this will not work if mp_inflate just got destroyed
+ // (no unget into previous compressed block)
mp_inflate->unget (n);
} else {
+ tl_assert (mp_buffer + n <= mp_bptr);
mp_bptr -= n;
m_blen += n;
m_pos -= n;
@@ -476,10 +609,11 @@ void InputStream::copy_to (tl::OutputStream &os)
}
void
-InputStream::inflate ()
+InputStream::inflate (bool stop_after)
{
tl_assert (mp_inflate == 0);
mp_inflate = new tl::InflateFilter (*this);
+ m_stop_after_inflate = stop_after;
}
void
diff --git a/src/tl/tl/tlStream.h b/src/tl/tl/tlStream.h
index 087e54f45..4e7cfdcf6 100644
--- a/src/tl/tl/tlStream.h
+++ b/src/tl/tl/tlStream.h
@@ -310,8 +310,7 @@ public:
* an error occurs - commonly if the command cannot be executed.
* This implementation is based on popen ().
*
- * @param cmd The command to execute
- * @param read True, if the file should be read, false on write.
+ * @param source The command to execute
*/
InputPipe (const std::string &source);
@@ -473,8 +472,11 @@ public:
* the uncompressed data rather than the raw data, until the
* compressed block is finished.
* The stream must not be in inflate state yet.
+ *
+ * If "stop_after" is true, the stream will stop after the inflated
+ * block has finished.
*/
- void inflate ();
+ void inflate (bool stop_after = false);
/**
* @brief Enables "inflate" right from the beginning
@@ -577,12 +579,87 @@ private:
// inflate support
InflateFilter *mp_inflate;
bool m_inflate_always;
+ bool m_stop_after_inflate;
// No copying currently
InputStream (const InputStream &);
InputStream &operator= (const InputStream &);
};
+/**
+ * @brief A wrapper that adds generic .gz support
+ */
+template
+class TL_PUBLIC_TEMPLATE inflating_input_stream
+ : public InputStreamBase
+{
+public:
+ inflating_input_stream (Base *delegate);
+
+ Base *delegate ()
+ {
+ return mp_delegate;
+ }
+
+ virtual size_t read (char *b, size_t n);
+
+ virtual void reset ()
+ {
+ m_inflating_stream.reset ();
+ enter_inflate ();
+ }
+
+ virtual void close ()
+ {
+ m_inflating_stream.close ();
+ }
+
+ virtual std::string source () const
+ {
+ return m_inflating_stream.source ();
+ }
+
+ virtual std::string absolute_path () const
+ {
+ return m_inflating_stream.absolute_path ();
+ }
+
+ virtual std::string filename () const
+ {
+ return m_inflating_stream.filename ();
+ }
+
+private:
+ tl::InputStream m_inflating_stream;
+ bool m_is_compressed;
+ Base *mp_delegate;
+
+ void enter_inflate ();
+ bool auto_detect_gz ();
+};
+
+/**
+ * @brief A pipe stream with .gz support
+ */
+class TL_PUBLIC InflatingInputPipe
+ : public inflating_input_stream
+{
+public:
+ /**
+ * @brief Open a stream by connecting with the stdout of a given command
+ *
+ * Opening a pipe is a prerequisite for reading from the
+ * object. open() will throw a FilePOpenErrorException if
+ * an error occurs - commonly if the command cannot be executed.
+ * This implementation is based on popen ().
+ *
+ * @param source The command to execute
+ */
+ InflatingInputPipe (const std::string &source)
+ : inflating_input_stream (new InputPipe (source))
+ { }
+};
+
// ---------------------------------------------------------------------------------
/**
diff --git a/src/tl/unit_tests/tlHttpStreamTests.cc b/src/tl/unit_tests/tlHttpStreamTests.cc
index e05c7e5fe..4acf10f3c 100644
--- a/src/tl/unit_tests/tlHttpStreamTests.cc
+++ b/src/tl/unit_tests/tlHttpStreamTests.cc
@@ -24,8 +24,10 @@
#include "tlHttpStream.h"
#include "tlUnitTest.h"
#include "tlTimer.h"
+#include "tlStream.h"
static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text");
+static std::string test_url1_gz ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text.gz");
static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1");
TEST(1)
@@ -125,3 +127,29 @@ TEST(3)
EXPECT_EQ (res, "hello, world.\n");
}
+// tl::Stream embedding
+TEST(4)
+{
+ if (! tl::InputHttpStream::is_available ()) {
+ throw tl::CancelException ();
+ }
+
+ tl::InputStream stream (test_url1);
+
+ std::string res = stream.read_all ();
+ EXPECT_EQ (res, "hello, world.\n");
+}
+
+// tl::Stream embedding with automatic unzip
+TEST(5)
+{
+ if (! tl::InputHttpStream::is_available ()) {
+ throw tl::CancelException ();
+ }
+
+ tl::InputStream stream (test_url1_gz);
+
+ std::string res = stream.read_all ();
+ EXPECT_EQ (res, "hello, world.\n");
+}
+
diff --git a/src/tl/unit_tests/tlStreamTests.cc b/src/tl/unit_tests/tlStreamTests.cc
index e51a7d4c4..517d71615 100644
--- a/src/tl/unit_tests/tlStreamTests.cc
+++ b/src/tl/unit_tests/tlStreamTests.cc
@@ -52,6 +52,13 @@ TEST(InputPipe2)
EXPECT_NE (ret, 0);
}
+TEST(InputPipe3)
+{
+ tl::InputStream str ("pipe:echo HELLOWORLD");
+ tl::TextInputStream tstr (str);
+ EXPECT_EQ (tstr.get_line (), "HELLOWORLD");
+}
+
TEST(OutputPipe1)
{
std::string tf = tmp_file ("pipe_out");
@@ -455,3 +462,5 @@ TEST(Backups)
}
}
+
+
From 26fc81624ca29a225669bcc7e188f7e8209725c4 Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Thu, 29 Feb 2024 21:45:56 +0100
Subject: [PATCH 15/63] Updating tests
---
src/tl/unit_tests/tlWebDAVTests.cc | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/tl/unit_tests/tlWebDAVTests.cc b/src/tl/unit_tests/tlWebDAVTests.cc
index 6d2948e7f..67598d832 100644
--- a/src/tl/unit_tests/tlWebDAVTests.cc
+++ b/src/tl/unit_tests/tlWebDAVTests.cc
@@ -70,6 +70,7 @@ TEST(1)
"[dir] dir1 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1/\n"
"[dir] dir2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir2/\n"
"text http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text\n"
+ "text.gz http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text.gz\n"
"text2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text2"
);
}
From c2187e0bf0f50589d58a4943cf4c255b297b6ac5 Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Thu, 29 Feb 2024 22:21:07 +0100
Subject: [PATCH 16/63] OASIS reader creates layers listed in layer map also if
empty
---
.../oasis/db_plugin/dbOASISReader.cc | 6 +++
.../oasis/unit_tests/dbOASISReaderTests.cc | 54 +++++++++++++++----
.../oasis/unit_tests/dbOASISWriterTests.cc | 7 +++
3 files changed, 58 insertions(+), 9 deletions(-)
diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc
index 1f7ab2b93..d9fd91ddd 100644
--- a/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc
+++ b/src/plugins/streamers/oasis/db_plugin/dbOASISReader.cc
@@ -1080,6 +1080,12 @@ OASISReader::do_read (db::Layout &layout)
LNameJoinOp2 op2;
layer_names ().add (l1, l2 + 1, dt_map, op2);
+ // for singular layers, force a layer entry:
+ // this way we can have empty, but existing layers.
+ if (l1 == l2 && dt1 == dt2) {
+ open_dl (layout, db::LDPair (l1, dt1));
+ }
+
reset_modal_variables ();
// ignore properties attached to this name item
diff --git a/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc b/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc
index b87a413c1..1ea0aa44e 100644
--- a/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc
+++ b/src/plugins/streamers/oasis/unit_tests/dbOASISReaderTests.cc
@@ -22,6 +22,7 @@
#include "dbOASISReader.h"
+#include "dbOASISWriter.h"
#include "dbTextWriter.h"
#include "dbTestSupport.h"
#include "tlLog.h"
@@ -487,21 +488,18 @@ TEST(99)
// XGEOMTRY tests (#773)
TEST(100)
{
- const char *expected =
+ const char *expected =
"begin_lib 0.0001\n"
"begin_cell {mask}\n"
- "boundary 7 1 {13237 5356} {13210 5490} {13192 5530} {13170 5563} {13130 5586} {13090 5583} {13070 5570} {13050 5551} {13037 5530} {13021 5490} {12988 5378} {12938 5390} {12963 5530} {12977 5570} {12998 5610} {13034 5650} {13051 5663} {13090 5678} {13130 5679} {13171 5667} {13210 5638} {13232 5611} {13253 5570} {13274 5490} {13291 5365} {13237 5356}\n"
- "boundary 4 0 {10772 1658} {10772 1744} {14510 1744} {14510 1658} {10772 1658}\n"
- "boundary 4 0 {14510 1658} {14510 1744} {15672 1744} {15672 1658} {14510 1658}\n"
- "boundary 4 0 {18157 647} {18157 676} {21642 676} {21642 647} {18157 647}\n"
- "boundary 6 0 {6743 2449} {6743 4230} {9061 4230} {9061 2449} {6743 2449}\n"
- "boundary 2 3 {21642 3613} {21642 4005} {19409 4005} {19409 6980} {21812 6980} {21812 4958} {21942 4958} {21942 4005} {21812 4005} {21812 3613} {21642 3613}\n"
- "boundary 2 4 {21642 4005} {21642 4958} {21812 4958} {21812 4005} {21642 4005}\n"
- "boundary 8 0 {21680 4106} {21640 4107} {21600 4118} {21574 4130} {21560 4138} {21520 4163} {21509 4170} {21480 4194} {21458 4210} {21440 4227} {21411 4250} {21400 4262} {21366 4290} {21360 4298} {21324 4330} {21320 4335} {21282 4370} {21280 4373} {21241 4410} {21240 4411} {21200 4450} {21160 4490} {21159 4490} {21039 4610} {21000 4650} {20960 4690} {20960 4691} {20921 4730} {20920 4732} {20896 4770} {20886 4810} {20882 4850} {20880 4930} {20880 5330} {20920 5370} {20960 5370} {21000 5340} {21013 5330} {21040 5325} {21080 5309} {21120 5291} {21121 5290} {21160 5276} {21200 5258} {21210 5250} {21240 5240} {21280 5222} {21295 5210} {21320 5202} {21360 5181} {21374 5170} {21400 5160} {21440 5136} {21447 5130} {21480 5112} {21510 5090} {21520 5086} {21560 5058} {21568 5050} {21600 5027} {21617 5010} {21640 4993} {21662 4970} {21680 4955} {21701 4930} {21720 4910} {21735 4890} {21760 4856} {21764 4850} {21786 4810} {21800 4781} {21805 4770} {21818 4730} {21828 4690} {21836 4650} {21840 4616} {21841 4610} {21845 4530} {21845 4450} {21844 4410} {21841 4370} {21840 4358} {21836 4330} {21829 4290} {21818 4250} {21803 4210} {21800 4205} {21778 4170} {21760 4148} {21738 4130} {21720 4118} {21680 4106}\n"
"boundary 1 0 {17922 6288} {17922 6510} {18150 6510} {18150 6288} {17922 6288}\n"
"boundary 1 0 {18157 647} {18157 676} {21630 676} {21630 647} {18157 647}\n"
"boundary 1 0 {21956 0} {21956 89} {22047 89} {22047 0} {21956 0}\n"
+ "boundary 2 3 {21642 3613} {21642 4005} {19409 4005} {19409 6980} {21812 6980} {21812 4958} {21942 4958} {21942 4005} {21812 4005} {21812 3613} {21642 3613}\n"
+ "boundary 2 4 {21642 4005} {21642 4958} {21812 4958} {21812 4005} {21642 4005}\n"
"boundary 3 0 {15392 1744} {15392 1774} {15672 1774} {15672 1744} {15392 1744}\n"
+ "boundary 4 0 {10772 1658} {10772 1744} {14510 1744} {14510 1658} {10772 1658}\n"
+ "boundary 4 0 {14510 1658} {14510 1744} {15672 1744} {15672 1658} {14510 1658}\n"
+ "boundary 4 0 {18157 647} {18157 676} {21642 676} {21642 647} {18157 647}\n"
"boundary 5 1 {15550 1658} {15550 1673} {15570 1673} {15570 1658} {15550 1658}\n"
"boundary 5 1 {15661 1657} {15641 1659} {15642 1671} {15662 1669} {15661 1657}\n"
"boundary 5 1 {18150 7440} {18150 7460} {18162 7460} {18162 7440} {18150 7440}\n"
@@ -519,6 +517,9 @@ TEST(100)
"boundary 5 1 {25710 1978} {25710 1998} {25722 1998} {25722 1978} {25710 1978}\n"
"boundary 5 1 {25710 2800} {25710 2820} {25722 2820} {25722 2800} {25710 2800}\n"
"boundary 5 2 {18074 6408} {17971 6486} {17983 6502} {18086 6424} {18074 6408}\n"
+ "boundary 6 0 {6743 2449} {6743 4230} {9061 4230} {9061 2449} {6743 2449}\n"
+ "boundary 7 1 {13237 5356} {13210 5490} {13192 5530} {13170 5563} {13130 5586} {13090 5583} {13070 5570} {13050 5551} {13037 5530} {13021 5490} {12988 5378} {12938 5390} {12963 5530} {12977 5570} {12998 5610} {13034 5650} {13051 5663} {13090 5678} {13130 5679} {13171 5667} {13210 5638} {13232 5611} {13253 5570} {13274 5490} {13291 5365} {13237 5356}\n"
+ "boundary 8 0 {21680 4106} {21640 4107} {21600 4118} {21574 4130} {21560 4138} {21520 4163} {21509 4170} {21480 4194} {21458 4210} {21440 4227} {21411 4250} {21400 4262} {21366 4290} {21360 4298} {21324 4330} {21320 4335} {21282 4370} {21280 4373} {21241 4410} {21240 4411} {21200 4450} {21160 4490} {21159 4490} {21039 4610} {21000 4650} {20960 4690} {20960 4691} {20921 4730} {20920 4732} {20896 4770} {20886 4810} {20882 4850} {20880 4930} {20880 5330} {20920 5370} {20960 5370} {21000 5340} {21013 5330} {21040 5325} {21080 5309} {21120 5291} {21121 5290} {21160 5276} {21200 5258} {21210 5250} {21240 5240} {21280 5222} {21295 5210} {21320 5202} {21360 5181} {21374 5170} {21400 5160} {21440 5136} {21447 5130} {21480 5112} {21510 5090} {21520 5086} {21560 5058} {21568 5050} {21600 5027} {21617 5010} {21640 4993} {21662 4970} {21680 4955} {21701 4930} {21720 4910} {21735 4890} {21760 4856} {21764 4850} {21786 4810} {21800 4781} {21805 4770} {21818 4730} {21828 4690} {21836 4650} {21840 4616} {21841 4610} {21845 4530} {21845 4450} {21844 4410} {21841 4370} {21840 4358} {21836 4330} {21829 4290} {21818 4250} {21803 4210} {21800 4205} {21778 4170} {21760 4148} {21738 4130} {21720 4118} {21680 4106}\n"
"end_cell\n"
"end_lib\n"
;
@@ -543,6 +544,41 @@ TEST(100)
EXPECT_EQ (std::string (os.string ()), std::string (expected))
}
+// Empty layers through LAYERMAP
+TEST(101)
+{
+ db::Layout ly;
+ ly.add_cell ("TOP");
+ ly.insert_layer (db::LayerProperties (1, 0, "A"));
+ ly.insert_layer (db::LayerProperties (2, 0, ""));
+ ly.insert_layer (db::LayerProperties (3, 0, "C"));
+
+ std::string tmp_file = tl::TestBase::tmp_file ("tmp_OASISReader101.oas");
+
+ {
+ tl::OutputStream stream (tmp_file);
+ db::OASISWriter writer;
+ db::SaveLayoutOptions options;
+ writer.write (ly, stream, options);
+ }
+
+ db::Layout ly_new;
+
+ {
+ tl::InputStream stream (tmp_file);
+ db::Reader reader (stream);
+ reader.read (ly_new);
+ }
+
+ // NOTE: only named layers are written into layer table
+ EXPECT_EQ (ly_new.cell_by_name ("TOP").first, true);
+ EXPECT_EQ (int (ly_new.layers ()), 2);
+ if (int (ly_new.layers ()) == 2) {
+ EXPECT_EQ (ly_new.get_properties (0).to_string (), "A (1/0)");
+ EXPECT_EQ (ly_new.get_properties (1).to_string (), "C (3/0)");
+ }
+}
+
TEST(Bug_121_1)
{
db::Manager m (false);
diff --git a/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc b/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc
index 6df3ff111..c85a7f600 100644
--- a/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc
+++ b/src/plugins/streamers/oasis/unit_tests/dbOASISWriterTests.cc
@@ -255,6 +255,13 @@ void run_test (tl::TestBase *_this, const char *file, bool scaling_test, int com
db::Reader reader (stream);
reader.read (layout);
+ // named layers create a mismatch between GDS and OASIS, so we unname them here
+ for (auto l = layout.begin_layers (); l != layout.end_layers (); ++l) {
+ db::LayerProperties lp = layout.get_properties ((*l).first);
+ lp.name.clear ();
+ layout.set_properties ((*l).first, lp);
+ }
+
db::SaveLayoutOptions options;
db::OASISWriterOptions oasis_options;
oasis_options.compression_level = compr;
From a431f70ad422e05812cb1b5bd209d6494a1d1d4a Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Thu, 29 Feb 2024 22:57:28 +0100
Subject: [PATCH 17/63] Polygon#break, DPolygon#break, SimplPolygon#break,
DSimplePolygon#break
---
src/db/db/gsiDeclDbPolygon.cc | 79 +++++++++++++++++++++++++++-------
testdata/ruby/dbPolygonTest.rb | 48 +++++++++++++++++++++
2 files changed, 111 insertions(+), 16 deletions(-)
diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc
index 417351c16..bc31beb55 100644
--- a/src/db/db/gsiDeclDbPolygon.cc
+++ b/src/db/db/gsiDeclDbPolygon.cc
@@ -31,6 +31,39 @@
namespace gsi
{
+template
+static std::vector split_poly (const C *p)
+{
+ std::vector parts;
+ db::split_polygon (*p, parts);
+ return parts;
+}
+
+template
+static void break_polygon (const C &poly, size_t max_vertex_count, double max_area_ratio, std::vector &result)
+{
+ if ((max_vertex_count > 0 && poly.vertices () > max_vertex_count) ||
+ (max_area_ratio > 0 && poly.area_ratio () > max_area_ratio)) {
+
+ std::vector split_polygons;
+ db::split_polygon (poly, split_polygons);
+ for (auto p = split_polygons.begin (); p != split_polygons.end (); ++p) {
+ break_polygon (*p, max_vertex_count, max_area_ratio, result);
+ }
+
+ } else {
+ result.push_back (poly);
+ }
+}
+
+template
+static std::vector break_poly (const C *p, size_t max_vertex_count, double max_area_ratio)
+{
+ std::vector parts;
+ break_polygon (*p, max_vertex_count, max_area_ratio, parts);
+ return parts;
+}
+
// ---------------------------------------------------------------
// simple polygon binding
@@ -245,13 +278,6 @@ struct simple_polygon_defs
return db::interact (*p, spoly);
}
- static std::vector split_poly (const C *p)
- {
- std::vector parts;
- db::split_polygon (*p, parts);
- return parts;
- }
-
static gsi::Methods methods ()
{
return
@@ -508,7 +534,7 @@ struct simple_polygon_defs
"\n"
"This method was introduced in version 0.25.\n"
) +
- method_ext ("split", &split_poly,
+ method_ext ("split", &split_poly,
"@brief Splits the polygon into two or more parts\n"
"This method will break the polygon into parts. The exact breaking algorithm is unspecified, the "
"result are smaller polygons of roughly equal number of points and 'less concave' nature. "
@@ -521,6 +547,20 @@ struct simple_polygon_defs
"\n"
"This method has been introduced in version 0.25.3."
) +
+ method_ext ("break", &break_poly, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio"),
+ "@brief Splits the polygon into parts with a maximum vertex count and area ratio\n"
+ "The area ratio is the ratio between the bounding box area and the polygon area. Higher values "
+ "mean more 'skinny' polygons.\n"
+ "\n"
+ "This method will split the input polygon into pieces having a maximum of 'max_vertex_count' vertices "
+ "and an area ratio less than 'max_area_ratio'. 'max_vertex_count' can be zero. In this case the "
+ "limit is ignored. Also 'max_area_ratio' can be zero, in which case it is ignored as well.\n"
+ "\n"
+ "The method of splitting is unspecified. The algorithm will apply 'split' recursively until the "
+ "parts satisfy the limits.\n"
+ "\n"
+ "This method has been introduced in version 0.29."
+ ) +
method_ext ("area", &area,
"@brief Gets the area of the polygon\n"
"The area is correct only if the polygon is not self-overlapping and the polygon is oriented clockwise."
@@ -1098,13 +1138,6 @@ struct polygon_defs
return db::interact (*p, spoly);
}
- static std::vector split_spoly (const C *p)
- {
- std::vector parts;
- db::split_polygon (*p, parts);
- return parts;
- }
-
static gsi::Methods methods ()
{
return
@@ -1520,7 +1553,7 @@ struct polygon_defs
"\n"
"This method was introduced in version 0.25.\n"
) +
- method_ext ("split", &split_spoly,
+ method_ext ("split", &split_poly,
"@brief Splits the polygon into two or more parts\n"
"This method will break the polygon into parts. The exact breaking algorithm is unspecified, the "
"result are smaller polygons of roughly equal number of points and 'less concave' nature. "
@@ -1533,6 +1566,20 @@ struct polygon_defs
"\n"
"This method has been introduced in version 0.25.3."
) +
+ method_ext ("break", &break_poly, gsi::arg ("max_vertex_count"), gsi::arg ("max_area_ratio"),
+ "@brief Splits the polygon into parts with a maximum vertex count and area ratio\n"
+ "The area ratio is the ratio between the bounding box area and the polygon area. Higher values "
+ "mean more 'skinny' polygons.\n"
+ "\n"
+ "This method will split the input polygon into pieces having a maximum of 'max_vertex_count' vertices "
+ "and an area ratio less than 'max_area_ratio'. 'max_vertex_count' can be zero. In this case the "
+ "limit is ignored. Also 'max_area_ratio' can be zero, in which case it is ignored as well.\n"
+ "\n"
+ "The method of splitting is unspecified. The algorithm will apply 'split' recursively until the "
+ "parts satisfy the limits.\n"
+ "\n"
+ "This method has been introduced in version 0.29."
+ ) +
method_ext ("area", &area,
"@brief Gets the area of the polygon\n"
"The area is correct only if the polygon is not self-overlapping and the polygon is oriented clockwise."
diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb
index bdd290f01..cb1910017 100644
--- a/testdata/ruby/dbPolygonTest.rb
+++ b/testdata/ruby/dbPolygonTest.rb
@@ -821,6 +821,54 @@ class DBPolygon_TestClass < TestBase
end
+ def test_breakPolygon
+
+ pts = []
+ pts << RBA::Point::new(0, 0)
+ pts << RBA::Point::new(0, 1000)
+ pts << RBA::Point::new(100, 1000)
+ pts << RBA::Point::new(100, 100)
+ pts << RBA::Point::new(1000, 100)
+ pts << RBA::Point::new(1000, 0)
+
+ split = RBA::Polygon::new(pts).break(4, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::Polygon::new(pts).break(0, 2.0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::Polygon::new(pts).break(0, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,1000;100,1000;100,100;1000,100;1000,0)")
+
+ split = RBA::SimplePolygon::new(pts).break(4, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::SimplePolygon::new(pts).break(0, 2.0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::SimplePolygon::new(pts).break(0, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,1000;100,1000;100,100;1000,100;1000,0)")
+
+ pts = []
+ pts << RBA::DPoint::new(0, 0)
+ pts << RBA::DPoint::new(0, 1000)
+ pts << RBA::DPoint::new(100, 1000)
+ pts << RBA::DPoint::new(100, 100)
+ pts << RBA::DPoint::new(1000, 100)
+ pts << RBA::DPoint::new(1000, 0)
+
+ split = RBA::DPolygon::new(pts).break(4, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::DPolygon::new(pts).break(0, 2.0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::DPolygon::new(pts).break(0, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,1000;100,1000;100,100;1000,100;1000,0)")
+
+ split = RBA::DSimplePolygon::new(pts).break(4, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::DSimplePolygon::new(pts).break(0, 2.0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,100;1000,100;1000,0);(0,100;0,1000;100,1000;100,100)")
+ split = RBA::DSimplePolygon::new(pts).break(0, 0)
+ assert_equal(split.collect { |p| p.to_s }.join(";"), "(0,0;0,1000;100,1000;100,100;1000,100;1000,0)")
+
+ end
+
def test_voidMethodsReturnSelf
hull = [ RBA::Point::new(0, 0), RBA::Point::new(6000, 0),
From bf8e392e73d3ca6687a07668d7543df910a82da4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 1 Mar 2024 21:43:41 +0000
Subject: [PATCH 18/63] Bump hmarr/debug-action from 2 to 3
Bumps [hmarr/debug-action](https://github.com/hmarr/debug-action) from 2 to 3.
- [Release notes](https://github.com/hmarr/debug-action/releases)
- [Commits](https://github.com/hmarr/debug-action/compare/v2...v3)
---
updated-dependencies:
- dependency-name: hmarr/debug-action
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7d9c02da0..dd59b39c6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -35,7 +35,7 @@ jobs:
dotnet: true
haskell: true
large-packages: true
- - uses: hmarr/debug-action@v2
+ - uses: hmarr/debug-action@v3
- name: Cancel Workflow Action
uses: styfle/cancel-workflow-action@0.12.1
- uses: actions/checkout@v4
From 4f615c6edcdf79cbd6492b446083c1fd96d6a79c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 1 Mar 2024 21:43:45 +0000
Subject: [PATCH 19/63] Bump pypa/gh-action-pypi-publish from 1.8.11 to 1.8.12
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.11 to 1.8.12.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.11...v1.8.12)
---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
.github/workflows/build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7d9c02da0..ff95ebcc6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -97,7 +97,7 @@ jobs:
name: artifact
path: dist
- - uses: pypa/gh-action-pypi-publish@v1.8.11
+ - uses: pypa/gh-action-pypi-publish@v1.8.12
continue-on-error: true # might fail if we don't bump the version
with:
user: __token__
@@ -114,7 +114,7 @@ jobs:
name: artifact
path: dist
- - uses: pypa/gh-action-pypi-publish@v1.8.11
+ - uses: pypa/gh-action-pypi-publish@v1.8.12
with:
user: __token__
password: ${{ secrets.pypi_password }}
From 1e323a0421d86704df16b9990742cf0796dce0c2 Mon Sep 17 00:00:00 2001
From: Matthias Koefferlein
Date: Fri, 1 Mar 2024 23:32:22 +0100
Subject: [PATCH 20/63] WIP
---
src/db/db/dbAsIfFlatEdges.cc | 274 ++++++++++----------
src/db/db/dbAsIfFlatEdges.h | 20 +-
src/db/db/dbDeepEdges.cc | 370 +---------------------------
src/db/db/dbDeepEdges.h | 8 +-
src/db/db/dbEdges.h | 40 +--
src/db/db/dbEdgesDelegate.h | 12 +-
src/db/db/dbEdgesLocalOperations.cc | 352 ++++++++++++++++++++++++++
src/db/db/dbEdgesLocalOperations.h | 74 ++++++
src/db/db/dbEmptyEdges.h | 12 +-
src/db/db/gsiDeclDbEdges.cc | 92 +++++--
10 files changed, 681 insertions(+), 573 deletions(-)
diff --git a/src/db/db/dbAsIfFlatEdges.cc b/src/db/db/dbAsIfFlatEdges.cc
index e4a482fe2..d48fbb0c3 100644
--- a/src/db/db/dbAsIfFlatEdges.cc
+++ b/src/db/db/dbAsIfFlatEdges.cc
@@ -28,6 +28,7 @@
#include "dbEmptyEdges.h"
#include "dbEdges.h"
#include "dbEdgesUtils.h"
+#include "dbEdgesLocalOperations.h"
#include "dbEdgeBoolean.h"
#include "dbBoxConvert.h"
#include "dbRegion.h"
@@ -39,6 +40,7 @@
#include "dbPolygonGenerators.h"
#include "dbPolygon.h"
#include "dbPath.h"
+#include "dbHierProcessor.h"
#include
@@ -94,104 +96,107 @@ AsIfFlatEdges::to_string (size_t nmax) const
return os.str ();
}
-EdgesDelegate *
-AsIfFlatEdges::selected_interacting_generic (const Region &other, EdgeInteractionMode mode, bool inverse) const
+namespace {
+
+class OutputPairHolder
{
- // shortcuts
- if (other.empty () || empty ()) {
- return ((mode == EdgesOutside) == inverse) ? new EmptyEdges () : clone ();
- }
+public:
+ OutputPairHolder (int inverse, bool merged_semantics)
+ {
+ m_e1.reset (new FlatEdges (merged_semantics));
+ m_results.push_back (& m_e1->raw_edges ());
- db::box_scanner2 scanner (report_progress (), progress_desc ());
-
- AddressableEdgeDelivery e (begin_merged ());
-
- for ( ; ! e.at_end (); ++e) {
- scanner.insert1 (e.operator-> (), 0);
- }
-
- AddressablePolygonDelivery p = (mode == EdgesInside ? other.addressable_merged_polygons () : other.addressable_polygons ());
-
- for ( ; ! p.at_end (); ++p) {
- scanner.insert2 (p.operator-> (), 1);
- }
-
- std::unique_ptr output (new FlatEdges (true));
-
- if (! inverse) {
-
- edge_to_region_interaction_filter filter (output.get (), mode);
- scanner.process (filter, 1, db::box_convert (), db::box_convert ());
-
- } else {
-
- std::set result;
- edge_to_region_interaction_filter > filter (&result, mode);
- scanner.process (filter, 1, db::box_convert (), db::box_convert ());
-
- for (EdgesIterator o (begin_merged ()); ! o.at_end (); ++o) {
- if (result.find (*o) == result.end ()) {
- output->insert (*o);
- }
+ if (inverse == 0) {
+ m_e2.reset (new FlatEdges (merged_semantics));
+ m_results.push_back (& m_e2->raw_edges ());
}
-
}
- return output.release ();
+ std::pair region_pair ()
+ {
+ return std::make_pair (m_e1.release (), m_e2.release ());
+ }
+
+ const std::vector &results () { return m_results; }
+
+private:
+ std::unique_ptr m_e1, m_e2;
+ std::vector m_results;
+};
+
}
EdgesDelegate *
-AsIfFlatEdges::selected_interacting_generic (const Edges &edges, EdgeInteractionMode mode, bool inverse) const
+AsIfFlatEdges::selected_interacting_generic (const Region &other, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const
{
+ min_count = std::max (size_t (1), min_count);
+
// shortcuts
- if (edges.empty () || empty ()) {
+ if (max_count < min_count || other.empty () || empty ()) {
return ((mode == EdgesOutside) == inverse) ? new EmptyEdges () : clone ();
}
- db::box_scanner scanner (report_progress (), progress_desc ());
+ bool counting = !(min_count == 1 && max_count == std::numeric_limits::max ());
+ OutputPairHolder oph (inverse ? 1 : -1, merged_semantics () || is_merged ());
- AddressableEdgeDelivery e (begin_merged ());
+ db::EdgesIterator edges (begin_merged ());
- for ( ; ! e.at_end (); ++e) {
- scanner.insert (e.operator-> (), 0);
+ db::edge_to_polygon_interacting_local_operation op (mode, inverse ? db::edge_to_polygon_interacting_local_operation::Inverse : db::edge_to_polygon_interacting_local_operation::Normal);
+
+ db::local_processor proc;
+ proc.set_base_verbosity (base_verbosity ());
+ proc.set_description (progress_desc ());
+ proc.set_report_progress (report_progress ());
+
+ std::vector > others;
+ // NOTE: with counting the other region needs to be merged
+ others.push_back (counting ? other.begin_merged () : other.begin ());
+
+ proc.run_flat (edges, others, std::vector (), &op, oph.results ());
+
+ return oph.region_pair ().first;
+}
+
+EdgesDelegate *
+AsIfFlatEdges::selected_interacting_generic (const Edges &other, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const
+{
+ min_count = std::max (size_t (1), min_count);
+
+ // @@@
+ // shortcuts
+ if (max_count < min_count || other.empty () || empty ()) {
+ return ((mode == EdgesOutside) == inverse) ? new EmptyEdges () : clone ();
}
- // NOTE: "inside" needs merged edges for the other edges as the algorithm works edge by edge
- AddressableEdgeDelivery ee = (mode == EdgesInside ? edges.addressable_merged_edges () : edges.addressable_edges ());
+ bool counting = !(min_count == 1 && max_count == std::numeric_limits::max ());
+ OutputPairHolder oph (inverse ? 1 : -1, merged_semantics () || is_merged ());
- for ( ; ! ee.at_end (); ++ee) {
- scanner.insert (ee.operator-> (), 1);
- }
+ db::EdgesIterator edges (begin_merged ());
- std::unique_ptr output (new FlatEdges (true));
+ db::Edge2EdgeInteractingLocalOperation op (mode, inverse ? db::Edge2EdgeInteractingLocalOperation::Inverse : db::Edge2EdgeInteractingLocalOperation::Normal);
- if (! inverse) {
+ db::local_processor proc;
+ proc.set_base_verbosity (base_verbosity ());
+ proc.set_description (progress_desc ());
+ proc.set_report_progress (report_progress ());
- edge_interaction_filter filter (*output, mode);
- scanner.process (filter, 1, db::box_convert ());
+ std::vector > others;
+ // NOTE: with counting the other edge collection needs to be merged
+ others.push_back (counting ? other.begin_merged () : other.begin ());
- } else {
+ proc.run_flat (edges, others, std::vector (), &op, oph.results ());
- std::set result;
- edge_interaction_filter > filter (result, mode);
- scanner.process (filter, 1, db::box_convert ());
-
- for (EdgesIterator o (begin_merged ()); ! o.at_end (); ++o) {
- if (result.find (*o) == result.end ()) {
- output->insert (*o);
- }
- }
-
- }
-
- return output.release ();
+ return oph.region_pair ().first;
}
std::pair
-AsIfFlatEdges::selected_interacting_pair_generic (const Region ®ion, EdgeInteractionMode mode) const
+AsIfFlatEdges::selected_interacting_pair_generic (const Region &other, EdgeInteractionMode mode, size_t min_count, size_t max_count) const
{
+ min_count = std::max (size_t (1), min_count);
+
+ // @@@
// shortcuts
- if (region.empty () || empty ()) {
+ if (max_count < min_count || other.empty () || empty ()) {
if (mode != EdgesOutside) {
return std::make_pair (new EmptyEdges (), clone ());
} else {
@@ -199,43 +204,35 @@ AsIfFlatEdges::selected_interacting_pair_generic (const Region ®ion, EdgeInte
}
}
- db::box_scanner2 scanner (report_progress (), progress_desc ());
+ bool counting = !(min_count == 1 && max_count == std::numeric_limits::max ());
+ OutputPairHolder oph (0, merged_semantics () || is_merged ());
- AddressableEdgeDelivery e (begin_merged ());
+ db::EdgesIterator edges (begin_merged ());
- for ( ; ! e.at_end (); ++e) {
- scanner.insert1 (e.operator-> (), 0);
- }
+ db::edge_to_polygon_interacting_local_operation op (mode, db::edge_to_polygon_interacting_local_operation::Both);
- AddressablePolygonDelivery p = region.addressable_merged_polygons ();
+ db::local_processor proc;
+ proc.set_base_verbosity (base_verbosity ());
+ proc.set_description (progress_desc ());
+ proc.set_report_progress (report_progress ());
- for ( ; ! p.at_end (); ++p) {
- scanner.insert2 (p.operator-> (), 1);
- }
+ std::vector > others;
+ // NOTE: with counting the other region needs to be merged
+ others.push_back (counting ? other.begin_merged () : other.begin ());
- std::unique_ptr output (new FlatEdges (true));
- std::unique_ptr output2 (new FlatEdges (true));
+ proc.run_flat (edges, others, std::vector (), &op, oph.results ());
- std::set result;
- edge_to_region_interaction_filter > filter (&result, mode);
- scanner.process (filter, 1, db::box_convert (), db::box_convert ());
-
- for (EdgesIterator o (begin_merged ()); ! o.at_end (); ++o) {
- if (result.find (*o) == result.end ()) {
- output2->insert (*o);
- } else {
- output->insert (*o);
- }
- }
-
- return std::make_pair (output.release (), output2.release ());
+ return oph.region_pair ();
}
std::pair
-AsIfFlatEdges::selected_interacting_pair_generic (const Edges &other, EdgeInteractionMode mode) const
+AsIfFlatEdges::selected_interacting_pair_generic (const Edges &other, EdgeInteractionMode mode, size_t min_count, size_t max_count) const
{
+ min_count = std::max (size_t (1), min_count);
+
+ // @@@
// shortcuts
- if (other.empty () || empty ()) {
+ if (max_count < min_count || other.empty () || empty ()) {
if (mode != EdgesOutside) {
return std::make_pair (new EmptyEdges (), clone ());
} else {
@@ -243,36 +240,25 @@ AsIfFlatEdges::selected_interacting_pair_generic (const Edges &other, EdgeIntera
}
}
- db::box_scanner scanner (report_progress (), progress_desc ());
+ bool counting = !(min_count == 1 && max_count == std::numeric_limits::max ());
+ OutputPairHolder oph (0, merged_semantics () || is_merged ());
- AddressableEdgeDelivery e (begin_merged ());
+ db::EdgesIterator edges (begin_merged ());
- for ( ; ! e.at_end (); ++e) {
- scanner.insert (e.operator-> (), 0);
- }
+ db::Edge2EdgeInteractingLocalOperation op (mode, db::Edge2EdgeInteractingLocalOperation::Both);
- AddressableEdgeDelivery ee = other.addressable_merged_edges ();
+ db::local_processor proc;
+ proc.set_base_verbosity (base_verbosity ());
+ proc.set_description (progress_desc ());
+ proc.set_report_progress (report_progress ());
- for ( ; ! ee.at_end (); ++ee) {
- scanner.insert (ee.operator-> (), 1);
- }
+ std::vector > others;
+ // NOTE: with counting the other edge collection needs to be merged
+ others.push_back (counting ? other.begin_merged () : other.begin ());
- std::unique_ptr output (new FlatEdges (true));
- std::unique_ptr output2 (new FlatEdges (true));
+ proc.run_flat (edges, others, std::vector (), &op, oph.results ());
- std::set results;
- edge_interaction_filter > filter (results, mode);
- scanner.process (filter, 1, db::box_convert ());
-
- for (EdgesIterator o (begin_merged ()); ! o.at_end (); ++o) {
- if (results.find (*o) == results.end ()) {
- output2->insert (*o);
- } else {
- output->insert (*o);
- }
- }
-
- return std::make_pair (output.release (), output2.release ());
+ return oph.region_pair ();
}
@@ -343,111 +329,111 @@ AsIfFlatEdges::pull_interacting (const Region &other) const
}
EdgesDelegate *
-AsIfFlatEdges::selected_interacting (const Edges &other) const
+AsIfFlatEdges::selected_interacting (const Edges &other, size_t min_count, size_t max_count) const
{
- return selected_interacting_generic (other, EdgesInteract, false);
+ return selected_interacting_generic (other, EdgesInteract, false, min_count, max_count);
}
EdgesDelegate *
-AsIfFlatEdges::selected_not_interacting (const Edges &other) const
+AsIfFlatEdges::selected_not_interacting (const Edges &other, size_t min_count, size_t max_count) const
{
- return selected_interacting_generic (other, EdgesInteract, true);
+ return selected_interacting_generic (other, EdgesInteract, true, min_count, max_count);
}
EdgesDelegate *
-AsIfFlatEdges::selected_interacting (const Region &other) const
+AsIfFlatEdges::selected_interacting (const Region &other, size_t min_count, size_t max_count) const
{
- return selected_interacting_generic (other, EdgesInteract, false);
+ return selected_interacting_generic (other, EdgesInteract, false, min_count, max_count);
}
EdgesDelegate *
-AsIfFlatEdges::selected_not_interacting (const Region &other) const
+AsIfFlatEdges::selected_not_interacting (const Region &other, size_t min_count, size_t max_count) const
{
- return selected_interacting_generic (other, EdgesInteract, true);
+ return selected_interacting_generic (other, EdgesInteract, true, min_count, max_count);
}
std::pair
-AsIfFlatEdges::selected_interacting_pair (const Region &other) const
+AsIfFlatEdges::selected_interacting_pair (const Region &other, size_t min_count, size_t max_count) const
{
- return selected_interacting_pair_generic (other, EdgesInteract);
+ return selected_interacting_pair_generic (other, EdgesInteract, min_count, max_count);
}
std::pair
-AsIfFlatEdges::selected_interacting_pair (const Edges &other) const
+AsIfFlatEdges::selected_interacting_pair (const Edges &other, size_t min_count, size_t max_count) const
{
- return selected_interacting_pair_generic (other, EdgesInteract);
+ return selected_interacting_pair_generic (other, EdgesInteract, min_count, max_count);
}
EdgesDelegate *
AsIfFlatEdges::selected_outside (const Region &other) const
{
- return selected_interacting_generic (other, EdgesOutside, false);
+ return selected_interacting_generic (other, EdgesOutside, false, size_t (0), std::numeric_limits::max ());
}
EdgesDelegate *
AsIfFlatEdges::selected_not_outside (const Region &other) const
{
- return selected_interacting_generic (other, EdgesOutside, true);
+ return selected_interacting_generic (other, EdgesOutside, true, size_t (0), std::numeric_limits::max ());
}
std::pair
AsIfFlatEdges::selected_outside_pair (const Region &other) const
{
- return selected_interacting_pair_generic (other, EdgesOutside);
+ return selected_interacting_pair_generic (other, EdgesOutside, size_t (0), std::numeric_limits::max ());
}
EdgesDelegate *
AsIfFlatEdges::selected_inside (const Region &other) const
{
- return selected_interacting_generic (other, EdgesInside, false);
+ return selected_interacting_generic (other, EdgesInside, false, size_t (0), std::numeric_limits::max ());
}
EdgesDelegate *
AsIfFlatEdges::selected_not_inside (const Region &other) const
{
- return selected_interacting_generic (other, EdgesInside, true);
+ return selected_interacting_generic (other, EdgesInside, true, size_t (0), std::numeric_limits::max ());
}
std::pair
AsIfFlatEdges::selected_inside_pair (const Region &other) const
{
- return selected_interacting_pair_generic (other, EdgesInside);
+ return selected_interacting_pair_generic (other, EdgesInside, size_t (0), std::numeric_limits::max ());
}
EdgesDelegate *
AsIfFlatEdges::selected_outside (const Edges &other) const
{
- return selected_interacting_generic (other, EdgesOutside, false);
+ return selected_interacting_generic (other, EdgesOutside, false, size_t (0), std::numeric_limits::max ());
}
EdgesDelegate *
AsIfFlatEdges::selected_not_outside (const Edges &other) const
{
- return selected_interacting_generic (other, EdgesOutside, true);
+ return selected_interacting_generic (other, EdgesOutside, true, size_t (0), std::numeric_limits::max ());
}
std::pair
AsIfFlatEdges::selected_outside_pair (const Edges &other) const
{
- return selected_interacting_pair_generic (other, EdgesOutside);
+ return selected_interacting_pair_generic (other, EdgesOutside, size_t (0), std::numeric_limits::max ());
}
EdgesDelegate *
AsIfFlatEdges::selected_inside (const Edges &other) const
{
- return selected_interacting_generic (other, EdgesInside, false);
+ return selected_interacting_generic (other, EdgesInside, false, size_t (0), std::numeric_limits::max ());
}
EdgesDelegate *
AsIfFlatEdges::selected_not_inside (const Edges &other) const
{
- return selected_interacting_generic (other, EdgesInside, true);
+ return selected_interacting_generic (other, EdgesInside, true, size_t (0), std::numeric_limits::max ());
}
std::pair
AsIfFlatEdges::selected_inside_pair (const Edges &other) const
{
- return selected_interacting_pair_generic (other, EdgesInside);
+ return selected_interacting_pair_generic (other, EdgesInside, size_t (0), std::numeric_limits::max ());
}
diff --git a/src/db/db/dbAsIfFlatEdges.h b/src/db/db/dbAsIfFlatEdges.h
index b1f9a07d4..fbdd7064e 100644
--- a/src/db/db/dbAsIfFlatEdges.h
+++ b/src/db/db/dbAsIfFlatEdges.h
@@ -183,12 +183,12 @@ public:
virtual EdgesDelegate *pull_interacting (const Edges &) const;
virtual RegionDelegate *pull_interacting (const Region &) const;
- virtual EdgesDelegate *selected_interacting (const Edges &) const;
- virtual EdgesDelegate *selected_not_interacting (const Edges &) const;
- virtual EdgesDelegate *selected_interacting (const Region &) const;
- virtual EdgesDelegate *selected_not_interacting (const Region &) const;
- virtual std::pair selected_interacting_pair (const Region &other) const;
- virtual std::pair selected_interacting_pair (const Edges &other) const;
+ virtual EdgesDelegate *selected_interacting (const Edges &, size_t min_count, size_t max_count) const;
+ virtual EdgesDelegate *selected_not_interacting (const Edges &, size_t min_count, size_t max_count) const;
+ virtual EdgesDelegate *selected_interacting (const Region &, size_t min_count, size_t max_count) const;
+ virtual EdgesDelegate *selected_not_interacting (const Region &, size_t min_count, size_t max_count) const;
+ virtual std::pair selected_interacting_pair (const Region &other, size_t min_count, size_t max_count) const;
+ virtual std::pair selected_interacting_pair (const Edges &other, size_t min_count, size_t max_count) const;
virtual EdgesDelegate *selected_outside (const Edges &other) const;
virtual EdgesDelegate *selected_not_outside (const Edges &other) const;
@@ -217,10 +217,10 @@ protected:
EdgePairsDelegate *run_check (db::edge_relation_type rel, const Edges *other, db::Coord d, const EdgesCheckOptions &options) const;
virtual EdgesDelegate *pull_generic (const Edges &edges) const;
virtual RegionDelegate *pull_generic (const Region ®ion) const;
- virtual EdgesDelegate *selected_interacting_generic (const Edges &edges, EdgeInteractionMode mode, bool inverse) const;
- virtual std::pair selected_interacting_pair_generic (const Edges &edges, EdgeInteractionMode mode) const;
- virtual EdgesDelegate *selected_interacting_generic (const Region ®ion, EdgeInteractionMode mode, bool inverse) const;
- virtual std::pair selected_interacting_pair_generic (const Region ®ion, EdgeInteractionMode mode) const;
+ virtual EdgesDelegate *selected_interacting_generic (const Edges &edges, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const;
+ virtual std::pair selected_interacting_pair_generic (const Edges &edges, EdgeInteractionMode mode, size_t min_count, size_t max_count) const;
+ virtual EdgesDelegate *selected_interacting_generic (const Region ®ion, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const;
+ virtual std::pair selected_interacting_pair_generic (const Region ®ion, EdgeInteractionMode mode, size_t min_count, size_t max_count) const;
AsIfFlatEdges &operator= (const AsIfFlatEdges &other);
AsIfFlatEdges (const AsIfFlatEdges &other);
diff --git a/src/db/db/dbDeepEdges.cc b/src/db/db/dbDeepEdges.cc
index 5a2812207..39d406d1b 100644
--- a/src/db/db/dbDeepEdges.cc
+++ b/src/db/db/dbDeepEdges.cc
@@ -1354,363 +1354,10 @@ RegionDelegate *DeepEdges::extended (coord_type ext_b, coord_type ext_e, coord_t
return res.release ();
}
-namespace
-{
-
-class Edge2EdgeInteractingLocalOperation
- : public local_operation
-{
-public:
- enum output_mode_t { Normal, Inverse, Both };
-
- Edge2EdgeInteractingLocalOperation (EdgeInteractionMode mode, output_mode_t output_mode)
- : m_mode (mode), m_output_mode (output_mode)
- {
- // .. nothing yet ..
- }
-
- virtual db::Coord dist () const
- {
- // touching is sufficient
- return 1;
- }
-
- virtual void do_compute_local (db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase * /*proc*/) const
- {
- tl_assert (results.size () == (m_output_mode == Both ? 2 : 1));
-
- std::unordered_set &result = results.front ();
-
- std::unordered_set *result2 = 0;
- if (m_output_mode == Both) {
- result2 = &results[1];
- }
-
- db::box_scanner scanner;
-
- std::set others;
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- for (shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) {
- others.insert (interactions.intruder_shape (*j).second);
- }
- }
-
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- const db::Edge &subject = interactions.subject_shape (i->first);
- scanner.insert (&subject, 0);
- }
-
- for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) {
- scanner.insert (o.operator-> (), 1);
- }
-
- if (m_output_mode == Inverse || m_output_mode == Both) {
-
- std::unordered_set interacting;
- edge_interaction_filter > filter (interacting, m_mode);
- scanner.process (filter, 1, db::box_convert ());
-
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
-
- const db::Edge &subject = interactions.subject_shape (i->first);
- if (interacting.find (subject) == interacting.end ()) {
- if (m_output_mode != Both) {
- result.insert (subject);
- } else {
- result2->insert (subject);
- }
- } else if (m_output_mode == Both) {
- result.insert (subject);
- }
-
- }
-
- } else {
-
- edge_interaction_filter > filter (result, m_mode);
- scanner.process (filter, 1, db::box_convert ());
-
- }
-
- }
-
- virtual OnEmptyIntruderHint on_empty_intruder_hint () const
- {
- if (m_mode == EdgesOutside) {
- return m_output_mode == Both ? Copy : (m_output_mode == Inverse ? Drop : Copy);
- } else {
- return m_output_mode == Both ? CopyToSecond : (m_output_mode == Inverse ? Copy : Drop);
- }
- }
-
- virtual std::string description () const
- {
- return tl::to_string (tr ("Select interacting edges"));
- }
-
-private:
- EdgeInteractionMode m_mode;
- output_mode_t m_output_mode;
-};
-
-class Edge2EdgePullLocalOperation
- : public local_operation
-{
-public:
- Edge2EdgePullLocalOperation ()
- {
- // .. nothing yet ..
- }
-
- virtual db::Coord dist () const
- {
- // touching is sufficient
- return 1;
- }
-
- virtual void do_compute_local (db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase * /*proc*/) const
- {
- tl_assert (results.size () == 1);
- std::unordered_set &result = results.front ();
-
- db::box_scanner scanner;
-
- std::set others;
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- for (shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) {
- others.insert (interactions.intruder_shape (*j).second);
- }
- }
-
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- const db::Edge &subject = interactions.subject_shape (i->first);
- scanner.insert (&subject, 1);
- }
-
- for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) {
- scanner.insert (o.operator-> (), 0);
- }
-
- edge_interaction_filter > filter (result, EdgesInteract);
- scanner.process (filter, 1, db::box_convert ());
-
- }
-
- virtual OnEmptyIntruderHint on_empty_intruder_hint () const
- {
- return Drop;
- }
-
- virtual std::string description () const
- {
- return tl::to_string (tr ("Select interacting edges from other"));
- }
-};
-
-class Edge2PolygonInteractingLocalOperation
- : public local_operation
-{
-public:
- enum output_mode_t { Normal, Inverse, Both };
-
- Edge2PolygonInteractingLocalOperation (EdgeInteractionMode mode, output_mode_t output_mode)
- : m_mode (mode), m_output_mode (output_mode)
- {
- // .. nothing yet ..
- }
-
- virtual db::Coord dist () const
- {
- // touching is sufficient
- return 1;
- }
-
- virtual void do_compute_local (db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase * /*proc*/) const
- {
- tl_assert (results.size () == size_t (m_output_mode == Both ? 2 : 1));
-
- std::unordered_set &result = results.front ();
-
- std::unordered_set *result2 = 0;
- if (m_output_mode == Both) {
- result2 = &results[1];
- }
-
- db::box_scanner2 scanner;
-
- std::set others;
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- for (shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) {
- others.insert (interactions.intruder_shape (*j).second);
- }
- }
-
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- const db::Edge &subject = interactions.subject_shape (i->first);
- scanner.insert1 (&subject, 0);
- }
-
- std::list heap;
- for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) {
- heap.push_back (o->obj ().transformed (o->trans ()));
- scanner.insert2 (& heap.back (), 1);
- }
-
- if (m_output_mode == Inverse || m_output_mode == Both) {
-
- std::unordered_set interacting;
- edge_to_region_interaction_filter > filter (&interacting, m_mode);
- scanner.process (filter, 1, db::box_convert (), db::box_convert ());
-
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
-
- const db::Edge &subject = interactions.subject_shape (i->first);
-
- if (interacting.find (subject) == interacting.end ()) {
- if (m_output_mode != Both) {
- result.insert (subject);
- } else {
- result2->insert (subject);
- }
- } else if (m_output_mode == Both) {
- result.insert (subject);
- }
-
- }
-
- } else {
-
- edge_to_region_interaction_filter > filter (&result, m_mode);
- scanner.process (filter, 1, db::box_convert (), db::box_convert ());
-
- }
- }
-
- virtual OnEmptyIntruderHint on_empty_intruder_hint () const
- {
- if (m_mode == EdgesOutside) {
- return m_output_mode == Both ? Copy : (m_output_mode == Inverse ? Drop : Copy);
- } else {
- return m_output_mode == Both ? CopyToSecond : (m_output_mode == Inverse ? Copy : Drop);
- }
- }
-
- virtual std::string description () const
- {
- if (m_mode == EdgesInteract) {
- if (m_output_mode == Inverse) {
- return tl::to_string (tr ("Select non-interacting edges"));
- } else if (m_output_mode == Normal) {
- return tl::to_string (tr ("Select interacting edges"));
- } else {
- return tl::to_string (tr ("Select interacting and non-interacting edges"));
- }
- } else if (m_mode == EdgesInside) {
- if (m_output_mode == Inverse) {
- return tl::to_string (tr ("Select non-inside edges"));
- } else if (m_output_mode == Normal) {
- return tl::to_string (tr ("Select inside edges"));
- } else {
- return tl::to_string (tr ("Select inside and non-inside edges"));
- }
- } else if (m_mode == EdgesOutside) {
- if (m_output_mode == Inverse) {
- return tl::to_string (tr ("Select non-outside edges"));
- } else if (m_output_mode == Normal) {
- return tl::to_string (tr ("Select outside edges"));
- } else {
- return tl::to_string (tr ("Select outside and non-outside edges"));
- }
- }
- return std::string ();
- }
-
-private:
- EdgeInteractionMode m_mode;
- output_mode_t m_output_mode;
-};
-
-struct ResultInserter
-{
- typedef db::Polygon value_type;
-
- ResultInserter (db::Layout *layout, std::unordered_set &result)
- : mp_layout (layout), mp_result (&result)
- {
- // .. nothing yet ..
- }
-
- void insert (const db::Polygon &p)
- {
- (*mp_result).insert (db::PolygonRef (p, mp_layout->shape_repository ()));
- }
-
-private:
- db::Layout *mp_layout;
- std::unordered_set *mp_result;
-};
-
-class Edge2PolygonPullLocalOperation
- : public local_operation
-{
-public:
- Edge2PolygonPullLocalOperation ()
- {
- // .. nothing yet ..
- }
-
- virtual db::Coord dist () const
- {
- // touching is sufficient
- return 1;
- }
-
- virtual void do_compute_local (db::Layout *layout, db::Cell * /*cell*/, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase * /*proc*/) const
- {
- tl_assert (results.size () == 1);
- std::unordered_set &result = results.front ();
-
- db::box_scanner2 scanner;
-
- std::set others;
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- for (shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) {
- others.insert (interactions.intruder_shape (*j).second);
- }
- }
-
- for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
- const db::Edge &subject = interactions.subject_shape (i->first);
- scanner.insert1 (&subject, 1);
- }
-
- std::list heap;
- for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) {
- heap.push_back (o->obj ().transformed (o->trans ()));
- scanner.insert2 (& heap.back (), 0);
- }
-
- ResultInserter inserter (layout, result);
- edge_to_region_interaction_filter filter (&inserter, EdgesInteract);
- scanner.process (filter, 1, db::box_convert (), db::box_convert ());
- }
-
- virtual OnEmptyIntruderHint on_empty_intruder_hint () const
- {
- return Drop;
- }
-
- virtual std::string description () const
- {
- return tl::to_string (tr ("Select interacting regions"));
- }
-};
-
-}
-
EdgesDelegate *
-DeepEdges::selected_interacting_generic (const Region &other, EdgeInteractionMode mode, bool inverse) const
+DeepEdges::selected_interacting_generic (const Region &other, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const
{
+ // @@@
std::unique_ptr dr_holder;
const db::DeepRegion *other_deep = dynamic_cast (other.delegate ());
if (! other_deep) {
@@ -1723,7 +1370,7 @@ DeepEdges::selected_interacting_generic (const Region &other, EdgeInteractionMod
DeepLayer dl_out (edges.derived ());
- db::Edge2PolygonInteractingLocalOperation op (mode, inverse ? db::Edge2PolygonInteractingLocalOperation::Inverse : db::Edge2PolygonInteractingLocalOperation::Normal);
+ db::edge_to_polygon_interacting_local_operation op (mode, inverse ? db::edge_to_polygon_interacting_local_operation::Inverse : db::edge_to_polygon_interacting_local_operation::Normal);
db::local_processor proc (const_cast (&edges.layout ()), const_cast (&edges.initial_cell ()), &other_deep->deep_layer ().layout (), &other_deep->deep_layer ().initial_cell (), edges.breakout_cells (), other_deep->deep_layer ().breakout_cells ());
proc.set_base_verbosity (base_verbosity ());
@@ -1735,8 +1382,9 @@ DeepEdges::selected_interacting_generic (const Region &other, EdgeInteractionMod
}
std::pair
-DeepEdges::selected_interacting_pair_generic (const Region &other, EdgeInteractionMode mode) const
+DeepEdges::selected_interacting_pair_generic (const Region &other, EdgeInteractionMode mode, size_t min_count, size_t max_count) const
{
+ // @@@
std::unique_ptr dr_holder;
const db::DeepRegion *other_deep = dynamic_cast (other.delegate ());
if (! other_deep) {
@@ -1755,7 +1403,7 @@ DeepEdges::selected_interacting_pair_generic (const Region &other, EdgeInteracti
output_layers.push_back (dl_out.layer ());
output_layers.push_back (dl_out2.layer ());
- db::Edge2PolygonInteractingLocalOperation op (mode, db::Edge2PolygonInteractingLocalOperation::Both);
+ db::edge_to_polygon_interacting_local_operation op (mode, db::edge_to_polygon_interacting_local_operation::Both);
db::local_processor proc (const_cast (&edges.layout ()), const_cast (&edges.initial_cell ()), &other_deep->deep_layer ().layout (), &other_deep->deep_layer ().initial_cell (), edges.breakout_cells (), other_deep->deep_layer ().breakout_cells ());
proc.set_base_verbosity (base_verbosity ());
@@ -1767,8 +1415,9 @@ DeepEdges::selected_interacting_pair_generic (const Region &other, EdgeInteracti
}
EdgesDelegate *
-DeepEdges::selected_interacting_generic (const Edges &other, EdgeInteractionMode mode, bool inverse) const
+DeepEdges::selected_interacting_generic (const Edges &other, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const
{
+ // @@@
std::unique_ptr dr_holder;
const db::DeepEdges *other_deep = dynamic_cast (other.delegate ());
if (! other_deep) {
@@ -1793,8 +1442,9 @@ DeepEdges::selected_interacting_generic (const Edges &other, EdgeInteractionMode
}
std::pair
-DeepEdges::selected_interacting_pair_generic (const Edges &other, EdgeInteractionMode mode) const
+DeepEdges::selected_interacting_pair_generic (const Edges &other, EdgeInteractionMode mode, size_t min_count, size_t max_count) const
{
+ // @@@
std::unique_ptr dr_holder;
const db::DeepEdges *other_deep = dynamic_cast (other.delegate ());
if (! other_deep) {
diff --git a/src/db/db/dbDeepEdges.h b/src/db/db/dbDeepEdges.h
index 0578d704b..7309f93da 100644
--- a/src/db/db/dbDeepEdges.h
+++ b/src/db/db/dbDeepEdges.h
@@ -193,10 +193,10 @@ private:
EdgePairsDelegate *run_check (db::edge_relation_type rel, const Edges *other, db::Coord d, const db::EdgesCheckOptions &options) const;
virtual EdgesDelegate *pull_generic (const Edges &edges) const;
virtual RegionDelegate *pull_generic (const Region ®ion) const;
- virtual EdgesDelegate *selected_interacting_generic (const Edges &edges, EdgeInteractionMode mode, bool inverse) const;
- virtual std::pair selected_interacting_pair_generic (const Edges &edges, EdgeInteractionMode mode) const;
- virtual EdgesDelegate *selected_interacting_generic (const Region ®ion, EdgeInteractionMode mode, bool inverse) const;
- virtual std::pair selected_interacting_pair_generic (const Region ®ion, EdgeInteractionMode mode) const;
+ virtual EdgesDelegate *selected_interacting_generic (const Edges &edges, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const;
+ virtual std::pair selected_interacting_pair_generic (const Edges &edges, EdgeInteractionMode mode, size_t min_count, size_t max_count) const;
+ virtual EdgesDelegate *selected_interacting_generic (const Region ®ion, EdgeInteractionMode mode, bool inverse, size_t min_count, size_t max_count) const;
+ virtual std::pair selected_interacting_pair_generic (const Region ®ion, EdgeInteractionMode mode, size_t min_count, size_t max_count) const;
DeepEdges *apply_filter (const EdgeFilterBase &filter) const;
template OutputContainer *processed_impl (const edge_processor &filter) const;
diff --git a/src/db/db/dbEdges.h b/src/db/db/dbEdges.h
index e5317f20d..28decbbb6 100644
--- a/src/db/db/dbEdges.h
+++ b/src/db/db/dbEdges.h
@@ -991,9 +991,9 @@ public:
* Merged semantics applies. If merged semantics is chosen, the connected edge parts will be
* selected as a whole.
*/
- Edges &select_interacting (const Region &other)
+ Edges &select_interacting (const Region &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ())
{
- set_delegate (mp_delegate->selected_interacting (other));
+ set_delegate (mp_delegate->selected_interacting (other, min_count, max_count));
return *this;
}
@@ -1002,9 +1002,9 @@ public:
*
* This method is an out-of-place version of select_interacting.
*/
- Edges selected_interacting (const Region &other) const
+ Edges selected_interacting (const Region &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ()) const
{
- return Edges (mp_delegate->selected_interacting (other));
+ return Edges (mp_delegate->selected_interacting (other, min_count, max_count));
}
/**
@@ -1013,9 +1013,9 @@ public:
* Merged semantics applies. If merged semantics is chosen, the connected edge parts will be
* selected as a whole.
*/
- Edges &select_not_interacting (const Region &other)
+ Edges &select_not_interacting (const Region &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ())
{
- set_delegate (mp_delegate->selected_not_interacting (other));
+ set_delegate (mp_delegate->selected_not_interacting (other, min_count, max_count));
return *this;
}
@@ -1024,17 +1024,17 @@ public:
*
* This method is an out-of-place version of select_not_interacting.
*/
- Edges selected_not_interacting (const Region &other) const
+ Edges selected_not_interacting (const Region &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ()) const
{
- return Edges (mp_delegate->selected_not_interacting (other));
+ return Edges (mp_delegate->selected_not_interacting (other, min_count, max_count));
}
/**
* @brief Returns all edges of this edge set which do not overlap or touch with polygons from the region together with the ones that do not
*/
- std::pair selected_interacting_differential (const Region &other) const
+ std::pair selected_interacting_differential (const Region &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ()) const
{
- std::pair p = mp_delegate->selected_interacting_pair (other);
+ std::pair p = mp_delegate->selected_interacting_pair (other, min_count, max_count);
return std::pair (Edges (p.first), Edges (p.second));
}
@@ -1280,9 +1280,9 @@ public:
* Merged semantics applies. If merged semantics is chosen, the connected edge parts will be
* selected as a whole.
*/
- Edges &select_interacting (const Edges &other)
+ Edges &select_interacting (const Edges &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ())
{
- set_delegate (mp_delegate->selected_interacting (other));
+ set_delegate (mp_delegate->selected_interacting (other, min_count, max_count));
return *this;
}
@@ -1291,17 +1291,17 @@ public:
*
* This method is an out-of-place version of select_interacting.
*/
- Edges selected_interacting (const Edges &other) const
+ Edges selected_interacting (const Edges &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ()) const
{
- return Edges (mp_delegate->selected_interacting (other));
+ return Edges (mp_delegate->selected_interacting (other, min_count, max_count));
}
/**
* @brief Returns all edges of this edge set which do not overlap or touch with edges from the other edge set together with the ones that do not
*/
- std::pair selected_interacting_differential (const Edges &other) const
+ std::pair selected_interacting_differential (const Edges &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ()) const
{
- std::pair p = mp_delegate->selected_interacting_pair (other);
+ std::pair p = mp_delegate->selected_interacting_pair (other, min_count, max_count);
return std::pair (Edges (p.first), Edges (p.second));
}
@@ -1311,9 +1311,9 @@ public:
* Merged semantics applies. If merged semantics is chosen, the connected edge parts will be
* selected as a whole.
*/
- Edges &select_not_interacting (const Edges &other)
+ Edges &select_not_interacting (const Edges &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ())
{
- set_delegate (mp_delegate->selected_not_interacting (other));
+ set_delegate (mp_delegate->selected_not_interacting (other, min_count, max_count));
return *this;
}
@@ -1322,9 +1322,9 @@ public:
*
* This method is an out-of-place version of select_not_interacting.
*/
- Edges selected_not_interacting (const Edges &other) const
+ Edges selected_not_interacting (const Edges &other, size_t min_count = 1, size_t max_count = std::numeric_limits::max ()) const
{
- return Edges (mp_delegate->selected_not_interacting (other));
+ return Edges (mp_delegate->selected_not_interacting (other, min_count, max_count));
}
/**
diff --git a/src/db/db/dbEdgesDelegate.h b/src/db/db/dbEdgesDelegate.h
index 919ba5b8a..e5210c24f 100644
--- a/src/db/db/dbEdgesDelegate.h
+++ b/src/db/db/dbEdgesDelegate.h
@@ -318,12 +318,12 @@ public:
virtual std::pair inside_outside_part_pair (const Region &other) const = 0;
virtual RegionDelegate *pull_interacting (const Region &) const = 0;
virtual EdgesDelegate *pull_interacting (const Edges &) const = 0;
- virtual EdgesDelegate *selected_interacting (const Region &other) const = 0;
- virtual EdgesDelegate *selected_not_interacting (const Region &other) const = 0;
- virtual EdgesDelegate *selected_interacting (const Edges &other) const = 0;
- virtual EdgesDelegate *selected_not_interacting (const Edges &other) const = 0;
- virtual std::pair selected_interacting_pair (const Region &other) const = 0;
- virtual std::pair selected_interacting_pair (const Edges &other) const = 0;
+ virtual EdgesDelegate *selected_interacting (const Region &other, size_t min_count, size_t max_count) const = 0;
+ virtual EdgesDelegate *selected_not_interacting (const Region &other, size_t min_count, size_t max_count) const = 0;
+ virtual EdgesDelegate *selected_interacting (const Edges &other, size_t min_count, size_t max_count) const = 0;
+ virtual EdgesDelegate *selected_not_interacting (const Edges &other, size_t min_count, size_t max_count) const = 0;
+ virtual std::pair selected_interacting_pair (const Region &other, size_t min_count, size_t max_count) const = 0;
+ virtual std::pair selected_interacting_pair (const Edges &other, size_t min_count, size_t max_count) const = 0;
virtual EdgesDelegate *selected_outside (const Region &other) const = 0;
virtual EdgesDelegate *selected_not_outside (const Region &other) const = 0;
diff --git a/src/db/db/dbEdgesLocalOperations.cc b/src/db/db/dbEdgesLocalOperations.cc
index e201df8b5..28bc77979 100644
--- a/src/db/db/dbEdgesLocalOperations.cc
+++ b/src/db/db/dbEdgesLocalOperations.cc
@@ -199,5 +199,357 @@ EdgeToPolygonLocalOperation::do_compute_local (db::Layout * /*layout*/, db::Cell
}
}
+// ---------------------------------------------------------------------------------------------
+// Edge2EdgeInteractingLocalOperation implementation
+
+Edge2EdgeInteractingLocalOperation::Edge2EdgeInteractingLocalOperation (EdgeInteractionMode mode, output_mode_t output_mode)
+ : m_mode (mode), m_output_mode (output_mode)
+{
+ // .. nothing yet ..
+}
+
+db::Coord Edge2EdgeInteractingLocalOperation::dist () const
+{
+ // touching is sufficient
+ return 1;
+}
+
+void Edge2EdgeInteractingLocalOperation::do_compute_local (db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase * /*proc*/) const
+{
+ tl_assert (results.size () == (m_output_mode == Both ? 2 : 1));
+
+ std::unordered_set &result = results.front ();
+
+ std::unordered_set *result2 = 0;
+ if (m_output_mode == Both) {
+ result2 = &results[1];
+ }
+
+ db::box_scanner scanner;
+
+ std::set others;
+ for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
+ for (shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) {
+ others.insert (interactions.intruder_shape (*j).second);
+ }
+ }
+
+ for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) {
+ const db::Edge &subject = interactions.subject_shape (i->first);
+ scanner.insert (&subject, 0);
+ }
+
+ for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) {
+ scanner.insert (o.operator-> (), 1);
+ }
+
+ if (m_output_mode == Inverse || m_output_mode == Both) {
+
+ std::unordered_set interacting;
+ edge_interaction_filter > filter (interacting, m_mode);
+ scanner.process (filter, 1, db::box_convert ());
+
+ for (shape_interactions