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:

"edges" - Decomposes the layer into single edges

+

Usage:

+
    +
  • layer.edges
  • +
  • layer.edges(mode)
  • +

Edge pair collections are decomposed into the individual edges that make up the edge pairs. Polygon layers are decomposed into the edges making up the @@ -791,6 +796,36 @@ is called on.

Merged semantics applies, i.e. the result reflects merged polygons rather than individual ones unless raw mode is chosen. +

+The "mode" argument allows selecting specific edges from polygons. +Allowed values are: "convex", "concave", "step", "step_in" and "step_out". +"step" generates edges only if they provide a step between two other +edges. "step_in" creates edges that make a step towards the inside of +the polygon and "step_out" creates edges that make a step towards the +outside: +

+

+out = in.edges(convex)
+
+

+This feature is only available for polygon layers. +

+The following images show the effect of the mode argument: +

+ + + + + + + + + + + + + +

"edges?" - Returns true, if the layer is an edge layer

diff --git a/src/doc/doc/images/drc_edge_modes1.png b/src/doc/doc/images/drc_edge_modes1.png new file mode 100644 index 0000000000000000000000000000000000000000..f5270d9443cde1b1763fae4163ddd44866fb1e01 GIT binary patch literal 7292 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 literal 0 HcmV?d00001 diff --git a/src/doc/doc/images/drc_edge_modes2.png b/src/doc/doc/images/drc_edge_modes2.png new file mode 100644 index 0000000000000000000000000000000000000000..5005e888d34c724542eec5ebba57326e7857b5e9 GIT binary patch literal 6912 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 H{XhH{s;UX3 literal 0 HcmV?d00001 diff --git a/src/doc/doc/images/drc_edge_modes3.png b/src/doc/doc/images/drc_edge_modes3.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc9c978b3aaed8b84c3ab8cf5fe50b77019de74 GIT binary patch literal 6808 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 Xo^M&Cd&}w+w8IWZXNUV+kNo&gVT_MI literal 0 HcmV?d00001 diff --git a/src/doc/doc/images/drc_edge_modes4.png b/src/doc/doc/images/drc_edge_modes4.png new file mode 100644 index 0000000000000000000000000000000000000000..94cf5014040eb688e9df6df502c13df5a83c6791 GIT binary patch literal 7165 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%0ATb!UTTBph zG3MFYctj^1|C8zwht~$f1!^sHi_9gusnLPAr>ZDw*d4P!OUOPy_@DzLJHDTwB~|*OBL%DRka-a<<8>TK*4+`TAgXb24Ajp_E`s54!u+>T^t`;h#N=h$!QopU%f0 z-HdFfVUo1wDRFHB?}kbI79bE$%$HN(q30Lyf;fws9(#Qu#zzz&$@w>5XAq|&QhyRP s#4kn{;rq7`QtUV6-?kUOS9B61Cmvj0)xVr&3gytwcCSsb^^u?d1^W@CUjP6A literal 0 HcmV?d00001 diff --git a/src/doc/doc/images/drc_edge_modes6.png b/src/doc/doc/images/drc_edge_modes6.png new file mode 100644 index 0000000000000000000000000000000000000000..e90b4380f7ab61dd945a9f8340041abb3ef11eed GIT binary patch literal 7052 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 alxTF{(+-PR;-`=V&n-LMOTYT=)c*iE&px&Q literal 0 HcmV?d00001 diff --git a/src/doc/docDRCLVSResources.qrc b/src/doc/docDRCLVSResources.qrc index e91766fe5..acf5f25a2 100644 --- a/src/doc/docDRCLVSResources.qrc +++ b/src/doc/docDRCLVSResources.qrc @@ -54,6 +54,12 @@ doc/images/drc_extended2.png doc/images/drc_extended3.png doc/images/drc_extended4.png + doc/images/drc_edge_modes1.png + doc/images/drc_edge_modes2.png + doc/images/drc_edge_modes3.png + doc/images/drc_edge_modes4.png + doc/images/drc_edge_modes5.png + doc/images/drc_edge_modes6.png doc/images/drc_extents1.png doc/images/drc_extents2.png doc/images/drc_inside.png 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 23b5b13b5..9950da2e9 100644 --- a/src/drc/drc/built-in-macros/_drc_complex_ops.rb +++ b/src/drc/drc/built-in-macros/_drc_complex_ops.rb @@ -1004,11 +1004,11 @@ CODE # @/code # # The "mode" argument allows selecting specific edges from polygons. - # Allows values are: "convex", "concave", "step", "step_in" and "step_out". - # "step" generates edges only that provide a step between two other + # Allowed values are: "convex", "concave", "step", "step_in" and "step_out". + # "step" generates edges only if they provide a step between two other # edges. "step_in" creates edges that make a step towards the inside of # the polygon and "step_out" creates edges that make a step towards the - # outside (hull contours in clockwise orientation, holes counterclockwise): + # outside: # # @code # out = in.drc(primary.edges(convex)) diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index fe89bb036..5d0409e2a 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -3399,17 +3399,34 @@ CODE # individual ones unless raw mode is chosen. # # The "mode" argument allows selecting specific edges from polygons. - # Allows values are: "convex", "concave", "step", "step_in" and "step_out". - # "step" generates edges only that provide a step between two other + # Allowed values are: "convex", "concave", "step", "step_in" and "step_out". + # "step" generates edges only if they provide a step between two other # edges. "step_in" creates edges that make a step towards the inside of # the polygon and "step_out" creates edges that make a step towards the - # outside (hull contours in clockwise orientation, holes counterclockwise): + # outside: # # @code # out = in.edges(convex) # @/code # # This feature is only available for polygon layers. + # + # The following images show the effect of the mode argument: + # + # @table + # @tr + # @td @img(/images/drc_edge_modes1.png) @/td + # @td @img(/images/drc_edge_modes2.png) @/td + # @/tr + # @tr + # @td @img(/images/drc_edge_modes3.png) @/td + # @td @img(/images/drc_edge_modes4.png) @/td + # @/tr + # @tr + # @td @img(/images/drc_edge_modes5.png) @/td + # @td @img(/images/drc_edge_modes6.png) @/td + # @/tr + # @/table %w(edges).each do |f| eval <<"CODE" From 28e96ee0c3d9809a52f77b4d46115c480a345f5f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 7 Mar 2024 22:29:33 +0100 Subject: [PATCH 42/63] Added not_... versions of edge modes --- src/db/db/dbRegionProcessors.cc | 45 +++++++++++++++--- src/db/db/dbRegionProcessors.h | 3 +- src/db/db/gsiDeclDbRegion.cc | 15 ++++++ .../drc/built-in-macros/_drc_complex_ops.rb | 7 +++ src/drc/drc/built-in-macros/_drc_engine.rb | 20 ++++++++ src/drc/drc/built-in-macros/_drc_layer.rb | 9 +++- testdata/drc/drcSimpleTests_92.drc | 11 +++++ testdata/drc/drcSimpleTests_au92.gds | Bin 14666 -> 36266 bytes testdata/drc/drcSimpleTests_au92d.gds | Bin 10176 -> 25296 bytes 9 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/db/db/dbRegionProcessors.cc b/src/db/db/dbRegionProcessors.cc index 9ba85b006..63fcdea3b 100644 --- a/src/db/db/dbRegionProcessors.cc +++ b/src/db/db/dbRegionProcessors.cc @@ -170,12 +170,45 @@ contour_to_edges (const db::Polygon::contour_type &contour, PolygonToEdgeProcess int s1 = db::vprod_sign (*p0 - *pm1, *p1 - *p0); int s2 = db::vprod_sign (*p1 - *p0, *p2 - *p1); - if (mode == PolygonToEdgeProcessor::All || - (mode == PolygonToEdgeProcessor::Convex && s1 < 0 && s2 < 0) || - (mode == PolygonToEdgeProcessor::Concave && s1 > 0 && s2 > 0) || - (mode == PolygonToEdgeProcessor::StepOut && s1 > 0 && s2 < 0) || - (mode == PolygonToEdgeProcessor::StepIn && s1 < 0 && s2 > 0) || - (mode == PolygonToEdgeProcessor::Step && s1 * s2 < 0)) { + bool take = true; + + switch (mode) { + case PolygonToEdgeProcessor::All: + default: + break; + case PolygonToEdgeProcessor::Convex: + take = s1 < 0 && s2 < 0; + break; + case PolygonToEdgeProcessor::NotConvex: + take = ! (s1 < 0 && s2 < 0); + break; + case PolygonToEdgeProcessor::Concave: + take = s1 > 0 && s2 > 0; + break; + case PolygonToEdgeProcessor::NotConcave: + take = ! (s1 > 0 && s2 > 0); + break; + case PolygonToEdgeProcessor::StepOut: + take = s1 > 0 && s2 < 0; + break; + case PolygonToEdgeProcessor::NotStepOut: + take = ! (s1 > 0 && s2 < 0); + break; + case PolygonToEdgeProcessor::StepIn: + take = s1 < 0 && s2 > 0; + break; + case PolygonToEdgeProcessor::NotStepIn: + take = ! (s1 < 0 && s2 > 0); + break; + case PolygonToEdgeProcessor::Step: + take = s1 * s2 < 0; + break; + case PolygonToEdgeProcessor::NotStep: + take = ! (s1 * s2 < 0); + break; + } + + if (take) { result.push_back (db::Edge (*p0, *p1)); } diff --git a/src/db/db/dbRegionProcessors.h b/src/db/db/dbRegionProcessors.h index 9cfc96fc5..08553ca25 100644 --- a/src/db/db/dbRegionProcessors.h +++ b/src/db/db/dbRegionProcessors.h @@ -293,7 +293,8 @@ class DB_PUBLIC PolygonToEdgeProcessor : public db::PolygonToEdgeProcessorBase { public: - enum EdgeMode { All = 0, Convex, Concave, StepIn, StepOut, Step }; + enum EdgeMode { All = 0, Convex, Concave, StepIn, StepOut, Step, + NotConvex, NotConcave, NotStepIn, NotStepOut, NotStep }; PolygonToEdgeProcessor (EdgeMode mode = All); diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index b51b1f4e9..2e16558ed 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -3258,17 +3258,32 @@ gsi::Enum decl_EdgeMode ("db", "EdgeMode", gsi::enum_const ("Concave", db::PolygonToEdgeProcessor::Concave, "@brief Selects only concave edges\n" ) + + gsi::enum_const ("NotConcave", db::PolygonToEdgeProcessor::NotConcave, + "@brief Selects only edges which are not concave\n" + ) + gsi::enum_const ("Convex", db::PolygonToEdgeProcessor::Convex, "@brief Selects only convex edges\n" ) + + gsi::enum_const ("NotConvex", db::PolygonToEdgeProcessor::NotConvex, + "@brief Selects only edges which are not convex\n" + ) + gsi::enum_const ("Step", db::PolygonToEdgeProcessor::Step, "@brief Selects only step edges leading inside or outside\n" ) + + gsi::enum_const ("NotStep", db::PolygonToEdgeProcessor::NotStep, + "@brief Selects only edges which are not steps\n" + ) + gsi::enum_const ("StepIn", db::PolygonToEdgeProcessor::StepIn, "@brief Selects only step edges leading inside\n" ) + + gsi::enum_const ("NotStepIn", db::PolygonToEdgeProcessor::NotStepIn, + "@brief Selects only edges which are not steps leading inside\n" + ) + gsi::enum_const ("StepOut", db::PolygonToEdgeProcessor::StepOut, "@brief Selects only step edges leading outside\n" + ) + + gsi::enum_const ("NotStepOut", db::PolygonToEdgeProcessor::NotStepOut, + "@brief Selects only edges which are not steps leading outside\n" ), "@brief This class represents the edge mode type for \\Region#edges.\n" "\n" 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 9950da2e9..93a659a95 100644 --- a/src/drc/drc/built-in-macros/_drc_complex_ops.rb +++ b/src/drc/drc/built-in-macros/_drc_complex_ops.rb @@ -1014,6 +1014,13 @@ CODE # out = in.drc(primary.edges(convex)) # @/code # + # In addition, "not_.." variants are available which selects edges + # not qualifying for the specific mode: + # + # @code + # out = in.drc(primary.edges(not_convex)) + # @/code + # # The mode argument is ignored when translating other objects than # polygons. diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 3714a325b..e7289f3f6 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -259,22 +259,42 @@ module DRC DRCEdgeMode::new(RBA::EdgeMode::Convex) end + def not_convex + DRCEdgeMode::new(RBA::EdgeMode::NotConvex) + end + def concave DRCEdgeMode::new(RBA::EdgeMode::Concave) end + def not_concave + DRCEdgeMode::new(RBA::EdgeMode::NotConcave) + end + def step_in DRCEdgeMode::new(RBA::EdgeMode::StepIn) end + def not_step_in + DRCEdgeMode::new(RBA::EdgeMode::NotStepIn) + end + def step_out DRCEdgeMode::new(RBA::EdgeMode::StepOut) end + def not_step_out + DRCEdgeMode::new(RBA::EdgeMode::NotStepOut) + end + def step DRCEdgeMode::new(RBA::EdgeMode::Step) end + def not_step + DRCEdgeMode::new(RBA::EdgeMode::NotStep) + end + def padding_zero DRCDensityPadding::new(:zero) end diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index 5d0409e2a..fc722810a 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -3409,7 +3409,14 @@ CODE # out = in.edges(convex) # @/code # - # This feature is only available for polygon layers. + # In addition, "not_.." variants are available which selects edges + # not qualifying for the specific mode: + # + # @code + # out = in.edges(not_convex) + # @/code + # + # The mode argument is only available for polygon layers. # # The following images show the effect of the mode argument: # diff --git a/testdata/drc/drcSimpleTests_92.drc b/testdata/drc/drcSimpleTests_92.drc index 5894b4c93..7187fe7aa 100644 --- a/testdata/drc/drcSimpleTests_92.drc +++ b/testdata/drc/drcSimpleTests_92.drc @@ -19,6 +19,12 @@ l2.edges(step).output(103, 0) l2.edges(step_in).output(104, 0) l2.edges(step_out).output(105, 0) +l2.edges(not_convex).output(111, 0) +l2.edges(not_concave).output(112, 0) +l2.edges(not_step).output(113, 0) +l2.edges(not_step_in).output(114, 0) +l2.edges(not_step_out).output(115, 0) + l2.drc(primary.edges).output(200, 0) l2.drc(primary.edges(convex)).output(201, 0) l2.drc(primary.edges(concave)).output(202, 0) @@ -26,4 +32,9 @@ l2.drc(primary.edges(step)).output(203, 0) l2.drc(primary.edges(step_in)).output(204, 0) l2.drc(primary.edges(step_out)).output(205, 0) +l2.drc(primary.edges(not_convex)).output(211, 0) +l2.drc(primary.edges(not_concave)).output(212, 0) +l2.drc(primary.edges(not_step)).output(213, 0) +l2.drc(primary.edges(not_step_in)).output(214, 0) +l2.drc(primary.edges(not_step_out)).output(215, 0) diff --git a/testdata/drc/drcSimpleTests_au92.gds b/testdata/drc/drcSimpleTests_au92.gds index 2ce998226d5f99532ba5ed4577e969a40e7ba7e6..26068b9b3bb8abd2c88391f2ce863219a089854d 100644 GIT binary patch delta 3467 zcmZXXJ8xW76oqGu@em$ik|+syl559O$OB;+#NZSZ{sTxZBSevZz|6gWfPX;9DFG=p zqA)^q6mCHjNOR}jHWi{wO~v}wzUK@U&Cxoq{a9?HQt*1 zJ$hv{8QmD|j%NSG-yGb#^1sEOKL4#6f0<5p<{qo`-G?`Yp6)UK^Bu;Q6UL)kK5>Wf z#chqx-m21%pXe}PdXHPDZ&c~&$DpI*D!sUw(ezEwSG(NZ|HyZNN5_mW9x8C#LbP!%lPBvU56WWg(R&%4-v>dXE)S+2I<~V1 zReF4#-Nkim!K~sFvti>%q6YfMr=Gme&h!Ju%)EIT_YzopO7iEPZ;&?CkP< zytFRMqDw}h%d+UQEV?Z1jS^dCRagpDGO{X5tAfQ)Vp)^`;Tt7%MFokc7()Y{7&HLI zPH3P=q`}gRFH4a!uM#{5g6E|Rb%|O~%*CKsjzw`Cgw{aNDmp}~0vD~3C}pe^L}tm& zWNv1V2|e?1Z8GaZW=26~UC68pnROwvcA+!6(Afw|Gcu*MDa9_{GO=|b7Q_z3>Y|9P zUE=r#=MvYsfnHGT1igw)^wzl{)aJP^JO`q(*$Ku2!FX-Pb$PG=?HCN&6_#jMK&t7u z1NngQRT zCdkHeuMR>@21EA!X7d-?lihCG4LH@RJqO#GJs+yWKe99p~;=5u?&pS zon>_B&ap<}2SSF%4W_XeHh~UU3>_4gbif27Cea}fI*32%pd6(`a6(rPgd)mVIut^z z!)AsKFgs7*@6Xih&3gLmV3y90Wi`Wj;6E)L1%+{)d z{fV}Ynu8&KYq?uX-`at&MAf?JZcQpH@wDiK=x?1NDYOm_`djBNXQIE&&C6c>70993 z=OQC{x32me@l5ml;&E!z|HB2PvROy1)?WngTWjuQPqk;)K}DugIZW+tD5Pu#m${4r zQH3%PvBi7N26I|h9O7q2_~zxeKOWO;)n6L>@vVU7%uz*LNDzon|1jtK+JQ^tzBfygF>hZhftN*XF}vo#)0^l0-@&GI^he} zG1a-kbz6L4lQI8>)O<(>o0~chYcQJEXF|C^10h%5-S+RH&T@AYEx|J8x(|df1QNnv zhBNtxt1d<}DOS;n{m=@;dA@3&r6(&p{S5Cf$j}#)eHNj&CcXO&heLqg0`ykAcfZk! z1n4b*)`w2hTS9tFAgJ%1Eb~omhhyZ(>ocKT+kvR^; z2sR2ZyV&;Y5hhVgiKs31?nB`C3!;=%L$WzE9;}B9++-)e++m5%!fEXg99^t9L>5vF RSwOyj=Wu5-8f|Zn{slrPHyr=~ delta 97 zcmZ2Ao9R@M5(66p6H^3(3?mc!3kGHeb_P)fT?SQT_CzIhK@7o-Q77dlpY@WSyg`6v da$J%4@|VP7%NI+HSF-l3ucr zjFU(I~=467rl`p z^UB_yVho7!uwyZW#$c~v3e8q%WDz4l(-cpOEHx5q90RhvrAC)A z8Z=Gu(P;N>X?%LN_;tZubb9GLZW54fGFKv%Cc;{x%FiOKiU~lB1HD?KuvfxrB~`~5 z&@{!zBsCg?y^3ixTceRpj08(1-OPQy8PMbVpyNU2_8)>itgH7P_GmkEe|MODvcWhf zQoZtB8Yo}E`a}TjGD);A+e^Rsl;S9XB(k|1tA_9niDgAn>SpfunDu-mb3fNW(704| z-p}00gt_xR-mE<92Op^BN;P+fW7aQjo6X$w2j<22cz_LJK#YeIESy)@@rBu|0k;F0 z(*ItUjC}!Z4mBWrDBg^Ms0JcU!z^l;33Jl`!aHb~Dd(AT#u{hV@q)A5KH+Ft;mE9U zl$$3U3q1k3Z=R{ldZvar0b7MLh!{8vmg*C4%-PSF7m;}pnS<~S%nPk~q5KQwk2U^y z;R}CQU{PT_P{Eq1C{;xX3p!V>z87MM?iCj=$>tCO;)w{YRH`K1MvsAQO-vcZlu-yl_5+Ja~R{L)=wC zvYCx3DTr|>oFIRLtC+Bg#ni2})V2c!sA7RZ`oKzoEC!Mw#^LNMEjDA#tAs;wt5|$8 zvCh4Q9X1?XrLyH|_DxgA(%BrkWX`ujO*xdat-`rhPIcr|D<{T0_MrhljLA-fy+HCa z5Ph|3#o8UWb>1$ru+D9hpMd8mKMCK?hB#hXm9sD(I5#nd*}%Wi% Date: Thu, 7 Mar 2024 23:03:30 +0100 Subject: [PATCH 43/63] Region#edges: Don't include an polygon to edge processor unless required --- src/db/db/gsiDeclDbRegion.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 2e16558ed..700cd2162 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -783,8 +783,13 @@ size_dvm (db::Region *region, const db::Vector &dv, unsigned int mode) static db::Edges edges (const db::Region *region, db::PolygonToEdgeProcessor::EdgeMode mode) { - db::PolygonToEdgeProcessor proc (mode); - return region->edges (proc); + if (mode != db::PolygonToEdgeProcessor::All) { + db::PolygonToEdgeProcessor proc (mode); + return region->edges (proc); + } else { + // this version is more efficient in the hierarchical case + return region->edges (); + } } static db::Point default_origin; From ea21a30367d63fd27351de5f41d7d558c7b35b1e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 7 Mar 2024 23:06:09 +0100 Subject: [PATCH 44/63] Update of test data --- testdata/drc/drcSimpleTests_au92d.gds | Bin 25296 -> 24756 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testdata/drc/drcSimpleTests_au92d.gds b/testdata/drc/drcSimpleTests_au92d.gds index 775f8f9fa1d94e3d1750f202ee871fac0359777f..e14a2cce22ad1f442fb8b4b5cf14b3457a5b34c7 100644 GIT binary patch delta 427 zcmZXPze)o^5XNWsHaT;WKMK0W@h(R|qL3nrrn0yqAXtPnL9xj_f~BR85dAs}wX#zq zu@sDmU}2F*u(Z<$5OMd8fRJMOX1>|^&CK{!JUojkWl72dS`pIyB!^tmq^S5mKCRZ~ zrp)`p3xN+;qbM0WvuxE<10p9RQUm;odclV7OlpZfO3qrr9Z!B(wHCsh!ih>iUN$1i z9Gk8&u4EmtY$C{{Y+JIhY?PFaT!!y(0!%w5K#$&t(r33Y5QTHOiIQrXSv-7l;`!-& z&{%v&TvcfAYJ6$NXjS2MPun2~*V%X#4BAQUtjoA8SEOtqsx6q1(cehcLC05~`$!}T zM7itazK+khS?(g<)s_7@Dy4D1YI4AKmi$n1$q>Vg=88>8kiPLAOfpUlrB zJz0U1W%2`d2)lrtWwITREz2xDxqw|7OwVAG2C563EWpAt`2&ymua01^^MbG+cu7y5FDX6Q!4E`B zPM#oTF}WaCdh%MSpvnB!Qb1aIazHSMFFkpK0)#g3gwO%95LzG#DsK&OVu2HczMuu7 jWhToiLY$Vb40hV&278Fx3arE@uLp(>)SH|ArBhu2aPxcx From 091995a5ff536a719893e7bdd2c910949bc30f71 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 8 Mar 2024 17:22:59 +0100 Subject: [PATCH 45/63] Limit effort spent on analyzing failed matches in LVS compare --- src/db/db/dbNetlistCompareCore.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/db/db/dbNetlistCompareCore.cc b/src/db/db/dbNetlistCompareCore.cc index 9e8abaef5..49558ad4d 100644 --- a/src/db/db/dbNetlistCompareCore.cc +++ b/src/db/db/dbNetlistCompareCore.cc @@ -1355,7 +1355,7 @@ static size_t distance3 (const NetGraphNode &a, const NetGraphNode &b1, const Ne static void analyze_nodes_for_close_matches (const std::multimap &nodes_by_edges1, const std::multimap &nodes_by_edges2, bool layout2ref, db::NetlistCompareLogger *logger, const db::NetGraph &g2) { - size_t max_search = 100; + size_t max_search = 100000; double max_fuzz_factor = 0.25; size_t max_fuzz_count = 3; size_t max_edges_split = 3; // by how many edges joining will reduce the edge count at max @@ -1368,7 +1368,9 @@ analyze_nodes_for_close_matches (const std::multimap 0; ++i) { if (i->first < min_edges) { continue; @@ -1376,7 +1378,7 @@ analyze_nodes_for_close_matches (const std::multimap seen; - for (auto j = nodes_by_edges2.begin (); j != nodes_by_edges2.end (); ++j) { + for (auto j = nodes_by_edges2.begin (); j != nodes_by_edges2.end () && tries > 0; ++j) { seen.insert (j->second); @@ -1407,7 +1409,6 @@ analyze_nodes_for_close_matches (const std::multimapfirst + k->first < i->first + max_fuzz_count + max_edges_split && tries > 0; ++k) { if (seen.find (k->second) != seen.end ()) { @@ -1436,6 +1437,8 @@ analyze_nodes_for_close_matches (const std::multimap= 21, "Analyzing failed matches"); + // Determine the range of nodes with same identity std::vector no_edges; From 856fe4a8d377ffe11adfd3908253a57bd61aca84 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 8 Mar 2024 22:00:20 +0100 Subject: [PATCH 46/63] LVS: Consider net names identical that differ in signal type suffix only - e.g. 'NET:I' is identical to 'NET' --- src/db/db/dbNetlist.cc | 14 ---- src/db/db/dbNetlist.h | 5 -- src/db/db/dbNetlistCompareUtils.cc | 33 +++++++- src/db/db/dbNetlistCompareUtils.h | 10 +-- src/db/unit_tests/dbNetlistCompareTests.cc | 96 ++++++++++++++++++++++ 5 files changed, 133 insertions(+), 25 deletions(-) diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc index 9c7c724ba..f0f8d177c 100644 --- a/src/db/db/dbNetlist.cc +++ b/src/db/db/dbNetlist.cc @@ -126,20 +126,6 @@ void Netlist::set_case_sensitive (bool f) m_case_sensitive = f; } -int Netlist::name_compare (bool case_sensitive, const std::string &n1, const std::string &n2) -{ - // TODO: unicode support? - if (case_sensitive) { - return strcmp (n1.c_str (), n2.c_str ()); - } else { -#if defined(_WIN32) - return _stricmp (n1.c_str (), n2.c_str ()); -#else - return strcasecmp (n1.c_str (), n2.c_str ()); -#endif - } -} - std::string Netlist::normalize_name (bool case_sensitive, const std::string &n) { if (case_sensitive) { diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index 397417d04..9e5221796 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -515,11 +515,6 @@ public: */ void combine_devices (); - /** - * @brief Compares two names with the given case sensitivity - */ - static int name_compare (bool case_sensitive, const std::string &n1, const std::string &n2); - /** * @brief Normalizes a name with the given case sensitivity */ diff --git a/src/db/db/dbNetlistCompareUtils.cc b/src/db/db/dbNetlistCompareUtils.cc index b4b50b9ca..97951ca25 100644 --- a/src/db/db/dbNetlistCompareUtils.cc +++ b/src/db/db/dbNetlistCompareUtils.cc @@ -125,9 +125,40 @@ const std::string &extended_net_name (const db::Net *n) } } +static int net_name_compare (bool case_sensitive, const std::string &n1, const std::string &n2) +{ + const char *n1p = n1.c_str (); + const char *n2p = n2.c_str (); + + while (*n1p && *n2p) { + + uint32_t c1 = tl::utf32_from_utf8 (n1p); + uint32_t c2 = tl::utf32_from_utf8 (n2p); + + if (! case_sensitive) { + c1 = tl::utf32_downcase (c1); + c2 = tl::utf32_downcase (c2); + } + + if (c1 != c2) { + return c1 < c2 ? -1 : 1; + } + + } + + // colon terminates net name, such that NET:I is identical to NET. + if (*n2p && *n2p != ':') { + return -1; + } else if (*n1p && *n1p != ':') { + return 1; + } else { + return 0; + } +} + int name_compare (const db::Net *a, const db::Net *b) { - return db::Netlist::name_compare (combined_case_sensitive (a->netlist (), b->netlist ()), extended_net_name (a), extended_net_name (b)); + return net_name_compare (combined_case_sensitive (a->netlist (), b->netlist ()), extended_net_name (a), extended_net_name (b)); } bool net_names_are_different (const db::Net *a, const db::Net *b) diff --git a/src/db/db/dbNetlistCompareUtils.h b/src/db/db/dbNetlistCompareUtils.h index bb4bd2948..55614577a 100644 --- a/src/db/db/dbNetlistCompareUtils.h +++ b/src/db/db/dbNetlistCompareUtils.h @@ -92,30 +92,30 @@ std::string nets2string (const std::pair &np); /** * @brief Derives the common case sensitivity for two netlists */ -bool combined_case_sensitive (const db::Netlist *a, const db::Netlist *b); +DB_PUBLIC bool combined_case_sensitive (const db::Netlist *a, const db::Netlist *b); /** * @brief Gets the extended net name * This name is used for comparing the net names and also employs the pin name if one is given */ -const std::string &extended_net_name (const db::Net *n); +DB_PUBLIC const std::string &extended_net_name (const db::Net *n); /** * @brief Compare two nets by name */ -int name_compare (const db::Net *a, const db::Net *b); +DB_PUBLIC int name_compare (const db::Net *a, const db::Net *b); /** * @brief Returns a value indicating whether two nets are different by name * Two unnamed nets are never different. */ -bool net_names_are_different (const db::Net *a, const db::Net *b); +DB_PUBLIC bool net_names_are_different (const db::Net *a, const db::Net *b); /** * @brief Returns a value indicating whether two nets are equal by name * Two unnamed nets are never equal. */ -bool net_names_are_equal (const db::Net *a, const db::Net *b); +DB_PUBLIC bool net_names_are_equal (const db::Net *a, const db::Net *b); // -------------------------------------------------------------------------------------------------------------------- // DeviceCompare definition and implementation diff --git a/src/db/unit_tests/dbNetlistCompareTests.cc b/src/db/unit_tests/dbNetlistCompareTests.cc index 6d75f7be3..edf6727dd 100644 --- a/src/db/unit_tests/dbNetlistCompareTests.cc +++ b/src/db/unit_tests/dbNetlistCompareTests.cc @@ -25,6 +25,7 @@ #include "dbNetlistCompare.h" #include "dbNetlistCrossReference.h" #include "dbNetlistSpiceReader.h" +#include "dbNetlistCompareUtils.h" class NetlistCompareTestLogger : public db::NetlistCompareLogger @@ -437,6 +438,101 @@ TEST(0_EqualDeviceParameters) EXPECT_EQ (dc.less (d2, d1), false); } +TEST(0_NetNameEquivalence) +{ + db::Netlist a, b; + a.set_case_sensitive (true); + b.set_case_sensitive (false); + + EXPECT_EQ (db::combined_case_sensitive (&a, &b), false); + + b.set_case_sensitive (true); + EXPECT_EQ (db::combined_case_sensitive (&a, &b), true); + + a.set_case_sensitive (false); + EXPECT_EQ (db::combined_case_sensitive (&a, &b), false); + + db::Circuit *ca = new db::Circuit (); + ca->set_name ("C"); + a.add_circuit (ca); + + db::Circuit *cb = new db::Circuit (); + cb->set_name ("C"); + b.add_circuit (cb); + + db::Net *na = new db::Net ("net1"); + ca->add_net (na); + + db::Net *nb = new db::Net ("net1"); + cb->add_net (nb); + + EXPECT_EQ (db::name_compare (na, nb), 0); + + nb->set_name ("NET1"); + EXPECT_EQ (db::name_compare (na, nb), 0); + + nb->set_name ("NET2"); + EXPECT_EQ (db::name_compare (na, nb), -1); + + nb->set_name ("NET11"); + EXPECT_EQ (db::name_compare (na, nb), -1); + + nb->set_name ("net11"); + EXPECT_EQ (db::name_compare (na, nb), -1); + + nb->set_name ("net0abc"); + EXPECT_EQ (db::name_compare (na, nb), 1); + + nb->set_name ("NET0"); + EXPECT_EQ (db::name_compare (na, nb), 1); + + a.set_case_sensitive (true); + b.set_case_sensitive (true); + + nb->set_name ("net1"); + EXPECT_EQ (db::name_compare (na, nb), 0); + + nb->set_name ("net2"); + EXPECT_EQ (db::name_compare (na, nb), -1); + + nb->set_name ("net11"); + EXPECT_EQ (db::name_compare (na, nb), -1); + + nb->set_name ("net0"); + EXPECT_EQ (db::name_compare (na, nb), 1); + + nb->set_name ("NET1"); + EXPECT_EQ (db::name_compare (na, nb), 1); + + na->set_name ("NET1"); + nb->set_name ("net1"); + EXPECT_EQ (db::name_compare (na, nb), -1); + + b.set_case_sensitive (false); + + // colon terminates the net name, so that NET:I and NET and identical + + na->set_name ("NET1:I"); + nb->set_name ("net1"); + EXPECT_EQ (db::name_compare (na, nb), 0); + + na->set_name ("NET1:I"); + nb->set_name ("net1:O"); + EXPECT_EQ (db::name_compare (na, nb), -1); + + na->set_name ("NET1"); + nb->set_name ("net1:O"); + EXPECT_EQ (db::name_compare (na, nb), 0); + + na->set_name ("NET2"); + nb->set_name ("net1:O"); + EXPECT_EQ (db::name_compare (na, nb), 1); + + na->set_name ("NET1"); + nb->set_name ("net1abc:O"); + EXPECT_EQ (db::name_compare (na, nb), -1); +} + TEST(1_SimpleInverter) { const char *nls1 = From 45950f20d698c9d22d8dca8862903c17114aa25a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 Mar 2024 00:50:46 +0100 Subject: [PATCH 47/63] Supporting remote must-connect connections So far, must-connect connections had to be made one level up in the hierarchy or promoted further using labels and such. Now, must-connect connections can be made at any point up in the hierarchy. --- src/db/db/dbLayoutToNetlist.cc | 101 +++-- src/db/db/dbLayoutToNetlist.h | 1 + src/lvs/unit_tests/lvsSimpleTests.cc | 6 + testdata/lvs/double_height2.lvsdb | 2 +- testdata/lvs/double_height2_texts.lvsdb | 2 +- testdata/lvs/must_connect2.lvsdb | 2 +- testdata/lvs/must_connect3.cir | 23 ++ testdata/lvs/must_connect3.gds | Bin 0 -> 4832 bytes testdata/lvs/must_connect3.lvs | 142 +++++++ testdata/lvs/must_connect3.lvsdb | 482 ++++++++++++++++++++++++ testdata/lvs/must_connect3.sch | 24 ++ 11 files changed, 754 insertions(+), 31 deletions(-) create mode 100644 testdata/lvs/must_connect3.cir create mode 100644 testdata/lvs/must_connect3.gds create mode 100644 testdata/lvs/must_connect3.lvs create mode 100644 testdata/lvs/must_connect3.lvsdb create mode 100644 testdata/lvs/must_connect3.sch diff --git a/src/db/db/dbLayoutToNetlist.cc b/src/db/db/dbLayoutToNetlist.cc index 62d2dc287..44b817a18 100644 --- a/src/db/db/dbLayoutToNetlist.cc +++ b/src/db/db/dbLayoutToNetlist.cc @@ -473,69 +473,114 @@ void LayoutToNetlist::check_must_connect (const db::Circuit &c, const db::Net &a return; } - if (c.begin_refs () != c.end_refs ()) { + std::vector path; + check_must_connect_impl (c, a, b, c, a, b, path); +} + +static std::string path_msg (const std::vector &path, const db::Circuit &c_org) +{ + if (path.empty ()) { + return std::string (); + } + + std::string msg (".\n" + tl::to_string (tr ("Instance path: "))); + + for (auto p = path.rbegin (); p != path.rend (); ++p) { + if (p != path.rbegin ()) { + msg += "/"; + } + msg += (*p)->circuit ()->name () + ":" + (*p)->expanded_name () + "[" + (*p)->trans ().to_string () + "]"; + } + + msg += "/"; + msg += c_org.name (); + + return msg; +} + +void LayoutToNetlist::check_must_connect_impl (const db::Circuit &c, const db::Net &a, const db::Net &b, const db::Circuit &c_org, const db::Net &a_org, const db::Net &b_org, std::vector &path) +{ + if (c.begin_refs () != c.end_refs () && path.empty ()) { + if (a.begin_pins () == a.end_pins ()) { - db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s is not connected to outside")), a.expanded_name ())); + db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s is not connected to outside")), a_org.expanded_name ())); error.set_cell_name (c.name ()); error.set_category_name ("must-connect"); log_entry (error); } if (b.begin_pins () == b.end_pins ()) { - db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s is not connected to outside")), a.expanded_name ())); + db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s is not connected to outside")), a_org.expanded_name ())); error.set_cell_name (c.name ()); error.set_category_name ("must-connect"); log_entry (error); } - } else { - if (a.expanded_name () == b.expanded_name ()) { - db::LogEntryData warn (m_top_level_mode ? db::Error : db::Warning, tl::sprintf (tl::to_string (tr ("Must-connect nets %s must be connected further up in the hierarchy - this is an error at chip top level")), a.expanded_name ())); - warn.set_cell_name (c.name ()); - warn.set_category_name ("must-connect"); - log_entry (warn); + + } else if (c.begin_refs () == c.end_refs () || a.begin_pins () == a.end_pins () || b.begin_pins () == b.end_pins ()) { + + if (a_org.expanded_name () == b_org.expanded_name ()) { + if (path.empty ()) { + db::LogEntryData warn (m_top_level_mode ? db::Error : db::Warning, tl::sprintf (tl::to_string (tr ("Must-connect nets %s must be connected further up in the hierarchy - this is an error at chip top level")), a_org.expanded_name ()) + path_msg (path, c_org)); + warn.set_cell_name (c.name ()); + warn.set_category_name ("must-connect"); + log_entry (warn); + } else { + db::LogEntryData warn (m_top_level_mode ? db::Error : db::Warning, tl::sprintf (tl::to_string (tr ("Must-connect nets %s of circuit %s must be connected further up in the hierarchy - this is an error at chip top level")), a_org.expanded_name (), c_org.name ()) + path_msg (path, c_org)); + warn.set_cell_name (c.name ()); + warn.set_geometry (subcircuit_geometry (*path.back (), internal_layout ())); + warn.set_category_name ("must-connect"); + log_entry (warn); + } } else { - db::LogEntryData warn (m_top_level_mode ? db::Error : db::Warning, tl::sprintf (tl::to_string (tr ("Must-connect nets %s and %s must be connected further up in the hierarchy - this is an error at chip top level")), a.expanded_name (), b.expanded_name ())); - warn.set_cell_name (c.name ()); - warn.set_category_name ("must-connect"); - log_entry (warn); + if (path.empty ()) { + db::LogEntryData warn (m_top_level_mode ? db::Error : db::Warning, tl::sprintf (tl::to_string (tr ("Must-connect nets %s and %s must be connected further up in the hierarchy - this is an error at chip top level")), a_org.expanded_name (), b_org.expanded_name ()) + path_msg (path, c_org)); + warn.set_cell_name (c.name ()); + warn.set_category_name ("must-connect"); + log_entry (warn); + } else { + db::LogEntryData warn (m_top_level_mode ? db::Error : db::Warning, tl::sprintf (tl::to_string (tr ("Must-connect nets %s and %s of circuit %s must be connected further up in the hierarchy - this is an error at chip top level")), a_org.expanded_name (), b_org.expanded_name (), c_org.name ()) + path_msg (path, c_org)); + warn.set_cell_name (c.name ()); + warn.set_geometry (subcircuit_geometry (*path.back (), internal_layout ())); + warn.set_category_name ("must-connect"); + log_entry (warn); + } } + } if (a.begin_pins () != a.end_pins () && b.begin_pins () != b.end_pins ()) { + for (auto ref = c.begin_refs (); ref != c.end_refs (); ++ref) { + const db::SubCircuit &sc = *ref; + // TODO: consider the case of multiple pins on a net (rare) const db::Net *net_a = sc.net_for_pin (a.begin_pins ()->pin_id ()); const db::Net *net_b = sc.net_for_pin (b.begin_pins ()->pin_id ()); + if (net_a == 0) { - db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s of circuit %s is not connected at all%s")), a.expanded_name (), c.name (), subcircuit_to_string (sc))); + db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s of circuit %s is not connected at all%s")), a_org.expanded_name (), c_org.name (), subcircuit_to_string (sc)) + path_msg (path, c_org)); error.set_cell_name (sc.circuit ()->name ()); error.set_geometry (subcircuit_geometry (sc, internal_layout ())); error.set_category_name ("must-connect"); log_entry (error); } + if (net_b == 0) { - db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s of circuit %s is not connected at all%s")), b.expanded_name (), c.name (), subcircuit_to_string (sc))); + db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect net %s of circuit %s is not connected at all%s")), b_org.expanded_name (), c_org.name (), subcircuit_to_string (sc)) + path_msg (path, c_org)); error.set_cell_name (sc.circuit ()->name ()); error.set_geometry (subcircuit_geometry (sc, internal_layout ())); error.set_category_name ("must-connect"); log_entry (error); } + if (net_a && net_b && net_a != net_b) { - if (a.expanded_name () == b.expanded_name ()) { - db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect nets %s of circuit %s are not connected%s")), a.expanded_name (), c.name (), subcircuit_to_string (sc))); - error.set_cell_name (sc.circuit ()->name ()); - error.set_geometry (subcircuit_geometry (sc, internal_layout ())); - error.set_category_name ("must-connect"); - log_entry (error); - } else { - db::LogEntryData error (db::Error, tl::sprintf (tl::to_string (tr ("Must-connect nets %s and %s of circuit %s are not connected%s")), a.expanded_name (), b.expanded_name (), c.name (), subcircuit_to_string (sc))); - error.set_cell_name (sc.circuit ()->name ()); - error.set_geometry (subcircuit_geometry (sc, internal_layout ())); - error.set_category_name ("must-connect"); - log_entry (error); - } + path.push_back (&sc); + check_must_connect_impl (*sc.circuit (), *net_a, *net_b, c_org, a_org, b_org, path); + path.pop_back (); } + } + } } diff --git a/src/db/db/dbLayoutToNetlist.h b/src/db/db/dbLayoutToNetlist.h index 493450194..2856de3e9 100644 --- a/src/db/db/dbLayoutToNetlist.h +++ b/src/db/db/dbLayoutToNetlist.h @@ -1041,6 +1041,7 @@ private: void join_nets_from_pattern (db::Circuit &c, const tl::GlobPattern &p); void join_nets_from_pattern (db::Circuit &c, const std::set &p); void check_must_connect (const db::Circuit &c, const db::Net &a, const db::Net &b); + void check_must_connect_impl (const db::Circuit &c, const db::Net &a, const db::Net &b, const db::Circuit &c_org, const db::Net &a_org, const db::Net &b_org, std::vector &path); // implementation of NetlistManipulationCallbacks virtual size_t link_net_to_parent_circuit (const Net *subcircuit_net, Circuit *parent_circuit, const DCplxTrans &trans); diff --git a/src/lvs/unit_tests/lvsSimpleTests.cc b/src/lvs/unit_tests/lvsSimpleTests.cc index d82d5d56d..a5603bd3f 100644 --- a/src/lvs/unit_tests/lvsSimpleTests.cc +++ b/src/lvs/unit_tests/lvsSimpleTests.cc @@ -289,6 +289,12 @@ TEST(31_MustConnect2) run_test (_this, "must_connect2", "must_connect2.gds"); } +// Intermediate cell propagates must-connect pins +TEST(32_MustConnect3) +{ + run_test (_this, "must_connect3", "must_connect3.gds"); +} + // issue 1609 TEST(40_DeviceExtractorErrors) { diff --git a/testdata/lvs/double_height2.lvsdb b/testdata/lvs/double_height2.lvsdb index cb79afe2b..a1ff6a9d4 100644 --- a/testdata/lvs/double_height2.lvsdb +++ b/testdata/lvs/double_height2.lvsdb @@ -29,7 +29,7 @@ J( G(l14 SUBSTRATE) H(W B('Must-connect nets GND must be connected further up in the hierarchy - this is an error at chip top level') C(INVCHAIN) X('must-connect')) H(W B('Must-connect nets R must be connected further up in the hierarchy - this is an error at chip top level') C(INVCHAIN) X('must-connect')) - H(E B('Must-connect nets R of circuit INV2 are not connected') C(INVCHAIN) X('must-connect') Q('(0,0;0,9.2;3,9.2;3,0)')) + H(W B('Must-connect nets R of circuit INV2 must be connected further up in the hierarchy - this is an error at chip top level.\nInstance path: INVCHAIN:$1[r0 *1 0,0]/INV2') C(INVCHAIN) X('must-connect') Q('(0,0;0,9.2;3,9.2;3,0)')) K(PMOS MOS3) K(NMOS MOS3) D(D$PMOS PMOS diff --git a/testdata/lvs/double_height2_texts.lvsdb b/testdata/lvs/double_height2_texts.lvsdb index 509b5b815..049cec097 100644 --- a/testdata/lvs/double_height2_texts.lvsdb +++ b/testdata/lvs/double_height2_texts.lvsdb @@ -29,7 +29,7 @@ J( G(l14 SUBSTRATE) H(W B('Must-connect nets GND must be connected further up in the hierarchy - this is an error at chip top level') C(INVCHAIN) X('must-connect')) H(W B('Must-connect nets R must be connected further up in the hierarchy - this is an error at chip top level') C(INVCHAIN) X('must-connect')) - H(E B('Must-connect nets R of circuit INV2 are not connected') C(INVCHAIN) X('must-connect') Q('(0,0;0,9.2;3,9.2;3,0)')) + H(W B('Must-connect nets R of circuit INV2 must be connected further up in the hierarchy - this is an error at chip top level.\nInstance path: INVCHAIN:$1[r0 *1 0,0]/INV2') C(INVCHAIN) X('must-connect') Q('(0,0;0,9.2;3,9.2;3,0)')) K(PMOS MOS3) K(NMOS MOS3) D(D$PMOS PMOS diff --git a/testdata/lvs/must_connect2.lvsdb b/testdata/lvs/must_connect2.lvsdb index b3fd8d3a3..02ee5a88c 100644 --- a/testdata/lvs/must_connect2.lvsdb +++ b/testdata/lvs/must_connect2.lvsdb @@ -29,7 +29,7 @@ J( G(l14 SUBSTRATE) H(W B('Must-connect nets VSSTOP must be connected further up in the hierarchy - this is an error at chip top level') C(TOP) X('must-connect')) H(W B('Must-connect nets VDD must be connected further up in the hierarchy - this is an error at chip top level') C(TOP) X('must-connect')) - H(E B('Must-connect nets VSS of circuit INV2 are not connected') C(INVCHAIN) X('must-connect') Q('(0,0;0,9.2;3,9.2;3,0)')) + H(W B('Must-connect nets VSS of circuit INV2 must be connected further up in the hierarchy - this is an error at chip top level.\nInstance path: INVCHAIN:$2[r0 *1 0,0]/INV2') C(INVCHAIN) X('must-connect') Q('(0,0;0,9.2;3,9.2;3,0)')) K(PMOS MOS3) K(NMOS MOS3) D(D$PMOS PMOS diff --git a/testdata/lvs/must_connect3.cir b/testdata/lvs/must_connect3.cir new file mode 100644 index 000000000..f9a636701 --- /dev/null +++ b/testdata/lvs/must_connect3.cir @@ -0,0 +1,23 @@ +* Extracted by KLayout + +.SUBCKT TOP +X$2 \$1 \$I2 \$I1 \$I1 \$1.Q \$2 INV2 +X$3 \$1.A \$I3 \$2 \$I3 \$I2 \$1 INVCHAIN +.ENDS TOP + +.SUBCKT INVCHAIN IN IN2 VSS|VSS2|VSS2B OUT OUT2 VDD +X$1 VDD IN2 \$1 \$1 OUT2 VSS|VSS2|VSS2B INV2 +X$2 VDD IN \$2 \$2 OUT VSS|VSS2|VSS2B INV2 +.ENDS INVCHAIN + +.SUBCKT INV2 VDD A1 A2 Q1 Q2 VSS +X$1 VSS VDD A2 Q2 INV +X$2 VSS VDD A1 Q1 INV +.ENDS INV2 + +.SUBCKT INV \$1 \$2 \$3 \$4 +M$1 \$2 \$3 \$4 \$4 PMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U ++ PD=3.45U +M$2 \$1 \$3 \$4 \$4 NMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U ++ PD=3.45U +.ENDS INV diff --git a/testdata/lvs/must_connect3.gds b/testdata/lvs/must_connect3.gds new file mode 100644 index 0000000000000000000000000000000000000000..e0115df231a1fe902af7fab5383380eac7d2f4d3 GIT binary patch literal 4832 zcmbW4TWDNW6o%KFnK^UGG)-bI&MldVqev-wH--<8A3kF-MB2v5rji5n;MnS~~MMe4`QiRs@MI|OV{`;~#XNNg6CkafxVV(2u zwJ&S`dk@)USr1SyWW}yfm=Z+PNBg|rDQ?+^$480U!_fofGy5i%F071<&G)~0ZhDGx zQGZ?1wx^DkD^;RMoXFl`kwtd1g$%n}!j7}{jN@$FEJzgknJ7HVJ3mIS>BolH(i8_4Me;w9q5(u+Y2YI8FQM@oq0P%Zkovy` zb|>_m7NHWlLKG$RQsm7>%LUbn>SY8QuJs>^r|n~7*zjA{C!Z!NEJ=M;IJjO_ z@vLh#_1J@r`yc&+D7!><+Ai-gvIkSIU}wB6)mT$c-{FjnbH`?oTdF6`+`-gmIrpT_ z-JteT(jV`-yF89{{=cFI)H>=lm1Efatn)1P>vc6geexR4FUN5UdV}%g;Zex}msuSL zW!ts%`R|D`k3$3IPNE*3bIEZ~>g%2yC9!XEZ=rnuBr3h)GJArk`v=VSiJ-064`MuT z5H%lmnJZu$<s>7>nukII%N}m@hC7t=Pp(j%ODA^hBLS=$}{2w;1%2=i#o;p_x={*E&XBWH$RQmf45_pG`4?X_Q2?G=ZVo>|zh+Gp~6?H5&h!S^xF z2Mg=|LCt*}$cK6!h7T=l--n7=+n#xy-`vZA@)t+o)>kPBAFNgfMz`T)@ikv34dg}X zbExl2Vg%zAY(QBddIbXMan?}wn(#8=4_o%Khp;zBMD`>1O_|vPxmh>596zb`dj7Rn z;OyaBKHm8Ry_{3bp`H`(V2@~W9Q5uFL*IitMCK2yHJJSkH3!FFavYTU27Zd)Bjc5+ zgGVu1IA%(o=E_~=`E6lFLfW-9JL>L7@a!p-MLQZhs(BY45{_2*3gbOavW7JHJgTDIG+tA~j672mDKS8mgOpb$f_11#YNB&iMCdWak zH+&R_nt30)b5Q;bsdGGe6H(je(#LAw#~SFx9%#F;eSII!&9*1EufG-d!SI^)(WxW% zZhZzit3<_JJ}vk-p*e?P)MNfpd8-TLWTtxXYup%q66#582ue_ii=O^$=U`k{5`m$A>lJoe7st1dYXN__(##P4pgQ2e`F zd!qC}Qh(lF7`revgKB}J3$~+)zRNB-4*KYEYrsmiRuwzDaO?UKHz!Sww^sf1YWIh) VC`M6vH~xhinWpZov- literal 0 HcmV?d00001 diff --git a/testdata/lvs/must_connect3.lvs b/testdata/lvs/must_connect3.lvs new file mode 100644 index 000000000..ac34742e7 --- /dev/null +++ b/testdata/lvs/must_connect3.lvs @@ -0,0 +1,142 @@ + +$lvs_test_source && source($lvs_test_source) + +if $lvs_test_target_lvsdb + report_lvs($lvs_test_target_lvsdb) +else + report_lvs +end + +# Implicit connection of the INV2 +# VSS nets +connect_implicit("INV2", "VSS") +connect_implicit("TOP", "VSS*") +# Fix 1 +connect_explicit("INVCHAIN", ["VSS2", "VSS2B", "VSS"]) +connect_implicit("INVCHAIN", "VDD") +connect_explicit("TOP", ["VDD"]) + +ignore_extraction_errors(true) + +writer = write_spice(true, false) +$lvs_test_target_cir && target_netlist($lvs_test_target_cir, writer, "Extracted by KLayout") + +# needs this delegate because we use MOS3 which is not available in Spice +class SpiceReaderDelegate < RBA::NetlistSpiceReaderDelegate + + # says we want to catch these subcircuits as devices + def wants_subcircuit(name) + name == "HVNMOS" || name == "HVPMOS" + end + + # translate the element + def element(circuit, el, name, model, value, nets, params) + + if el != "M" + # all other elements are left to the standard implementation + return super + end + + if nets.size != 4 + error("Device #{model} needs four nodes") + end + + # provide a device class + cls = circuit.netlist.device_class_by_name(model) + if ! cls + cls = RBA::DeviceClassMOS3Transistor::new + cls.name = model + circuit.netlist.add(cls) + end + + # create a device + device = circuit.create_device(cls, name) + + # and configure the device + [ "S", "G", "D" ].each_with_index do |t,index| + device.connect_terminal(t, nets[index]) + end + device.set_parameter("W", params["W"] * 1e6) + device.set_parameter("L", params["L"] * 1e6) + + device + + end + +end + +reader = RBA::NetlistSpiceReader::new(SpiceReaderDelegate::new) +schematic(File.basename(source.path, ".*") + ".sch", reader) + +deep + +# Drawing layers + +nwell = input(1, 0) +active = input(2, 0) +poly = input(3, 0) +poly_lbl = input(3, 1) +diff_cont = input(4, 0) +poly_cont = input(5, 0) +metal1 = input(6, 0) +metal1_lbl = input(6, 1) +via1 = input(7, 0) +metal2 = input(8, 0) +metal2_lbl = input(8, 1) + +# Bulk layer for terminal provisioning + +bulk = polygon_layer + +psd = nil +nsd = nil + +# Computed layers + +active_in_nwell = active & nwell +pactive = active_in_nwell +pgate = pactive & poly +psd = pactive - pgate + +active_outside_nwell = active - nwell +nactive = active_outside_nwell +ngate = nactive & poly +nsd = nactive - ngate + +# Device extraction + +# PMOS transistor device extraction +extract_devices(mos3("PMOS"), { "SD" => psd, "G" => pgate, + "tS" => psd, "tD" => psd, "tG" => poly }) + +# NMOS transistor device extraction +extract_devices(mos3("NMOS"), { "SD" => nsd, "G" => ngate, + "tS" => nsd, "tD" => nsd, "tG" => poly }) + +# Define connectivity for netlist extraction + +# Inter-layer +connect(psd, diff_cont) +connect(nsd, diff_cont) +connect(poly, poly_cont) +connect(diff_cont, metal1) +connect(poly_cont, metal1) +connect(metal1, via1) +connect(via1, metal2) + +# attach labels +connect(poly, poly_lbl) +connect(metal1, metal1_lbl) +connect(metal2, metal2_lbl) + +# Global +connect_global(bulk, "SUBSTRATE") + +# Compare section + +netlist.simplify +align + +# Skip as we have errors .. +compare + diff --git a/testdata/lvs/must_connect3.lvsdb b/testdata/lvs/must_connect3.lvsdb new file mode 100644 index 000000000..83f89832d --- /dev/null +++ b/testdata/lvs/must_connect3.lvsdb @@ -0,0 +1,482 @@ +#%lvsdb-klayout +J( + W(TOP) + U(0.001) + L(l3 '3/0') + L(l11 '3/1') + L(l6 '4/0') + L(l7 '5/0') + L(l8 '6/0') + L(l12 '6/1') + L(l9 '7/0') + L(l10 '8/0') + L(l13 '8/1') + L(l14) + L(l2) + L(l5) + C(l3 l3 l11 l7) + C(l11 l3 l11) + C(l6 l6 l8 l2 l5) + C(l7 l3 l7 l8) + C(l8 l6 l7 l8 l12 l9) + C(l12 l8 l12) + C(l9 l8 l9 l10) + C(l10 l9 l10 l13) + C(l13 l10 l13) + C(l14 l14) + C(l2 l6 l2) + C(l5 l6 l5) + G(l14 SUBSTRATE) + K(PMOS MOS3) + K(NMOS MOS3) + D(D$PMOS PMOS + T(S + R(l2 (-900 -475) (775 950)) + ) + T(G + R(l3 (-125 -475) (250 950)) + ) + T(D + R(l2 (125 -475) (775 950)) + ) + ) + D(D$NMOS NMOS + T(S + R(l5 (-900 -475) (775 950)) + ) + T(G + R(l3 (-125 -475) (250 950)) + ) + T(D + R(l5 (125 -475) (775 950)) + ) + ) + X(INV + R((-1500 -800) (3000 4600)) + N(1 + R(l6 (290 -310) (220 220)) + R(l6 (-220 180) (220 220)) + R(l8 (-290 -690) (360 760)) + R(l9 (-305 -705) (250 250)) + R(l9 (-250 150) (250 250)) + R(l10 (-2025 -775) (3000 900)) + R(l5 (-1375 -925) (775 950)) + ) + N(2 + R(l6 (290 2490) (220 220)) + R(l6 (-220 180) (220 220)) + R(l8 (-290 -690) (360 760)) + R(l9 (-305 -705) (250 250)) + R(l9 (-250 150) (250 250)) + R(l10 (-2025 -775) (3000 900)) + R(l2 (-1375 -925) (775 950)) + ) + N(3 + R(l3 (-125 -250) (250 2500)) + R(l3 (-250 -3050) (250 1600)) + R(l3 (-250 1200) (250 1600)) + ) + N(4 + R(l6 (-510 -310) (220 220)) + R(l6 (-220 180) (220 220)) + R(l6 (-220 2180) (220 220)) + R(l6 (-220 180) (220 220)) + R(l8 (-290 -3530) (360 2840)) + R(l8 (-360 -2800) (360 760)) + R(l8 (-360 2040) (360 760)) + R(l2 (-680 -855) (775 950)) + R(l5 (-775 -3750) (775 950)) + ) + P(1) + P(2) + P(3) + P(4) + D(1 D$PMOS + Y(0 2800) + E(L 0.25) + E(W 0.95) + E(AS 0.73625) + E(AD 0.73625) + E(PS 3.45) + E(PD 3.45) + T(S 4) + T(G 3) + T(D 2) + ) + D(2 D$NMOS + Y(0 0) + E(L 0.25) + E(W 0.95) + E(AS 0.73625) + E(AD 0.73625) + E(PS 3.45) + E(PD 3.45) + T(S 4) + T(G 3) + T(D 1) + ) + ) + X(INV2 + R((0 0) (3000 9200)) + N(1 I(VDD) + R(l10 (0 3150) (3000 2900)) + R(l13 (-1890 -1450) (0 0)) + ) + N(2 I(A1) + R(l11 (1480 7110) (0 0)) + ) + N(3 I(A2) + R(l11 (1520 1950) (0 0)) + ) + N(4 I(Q1) + R(l12 (1920 7070) (0 0)) + ) + N(5 I(Q2) + R(l12 (1940 1950) (0 0)) + ) + N(6 I(VSS) + R(l13 (2680 8390) (0 0)) + R(l13 (-30 -7640) (0 0)) + ) + P(1 I(VDD)) + P(2 I(A1)) + P(3 I(A2)) + P(4 I(Q1)) + P(5 I(Q2)) + P(6 I(VSS)) + X(1 INV M O(180) Y(1500 800) + P(0 6) + P(1 1) + P(2 3) + P(3 5) + ) + X(2 INV O(180) Y(1500 8400) + P(0 6) + P(1 1) + P(2 2) + P(3 4) + ) + ) + X(INVCHAIN + R((-915 -15) (10415 9215)) + N(1 + R(l3 (7340 1650) (2160 250)) + R(l3 (-250 0) (250 4990)) + R(l3 (-1605 0) (1605 250)) + R(l7 (-1545 -250) (240 250)) + R(l8 (-560 -375) (690 510)) + ) + N(2 + R(l3 (1625 1835) (2160 250)) + R(l3 (-250 0) (250 4990)) + R(l3 (-1605 0) (1605 250)) + R(l7 (-1545 -250) (240 250)) + R(l8 (-560 -375) (690 510)) + ) + N(3 I(IN) + R(l3 (-90 6850) (1590 650)) + R(l11 (-700 -350) (0 0)) + ) + N(4 I(IN2) + R(l3 (5665 6790) (1590 650)) + R(l11 (-700 -350) (0 0)) + ) + N(5 I('VSS,VSS2,VSS2B') + R(l10 (-915 5290) (250 2960)) + R(l10 (-250 0) (915 250)) + R(l10 (-915 -7825) (915 250)) + R(l10 (-915 0) (250 3145)) + R(l13 (155 4305) (0 0)) + R(l13 (8990 -255) (0 0)) + R(l13 (25 -7115) (0 0)) + ) + N(6 I(OUT) + R(l12 (1890 2105) (0 0)) + ) + N(7 I(OUT2) + R(l12 (7730 2155) (0 0)) + ) + N(8 I(VDD) + R(l13 (8035 4540) (0 0)) + R(l13 (-5735 60) (0 0)) + ) + P(3 I(IN)) + P(4 I(IN2)) + P(5 I('VSS,VSS2,VSS2B')) + P(6 I(OUT)) + P(7 I(OUT2)) + P(8 I(VDD)) + X(1 INV2 Y(5780 -15) + P(0 8) + P(1 4) + P(2 1) + P(3 1) + P(4 7) + P(5 5) + ) + X(2 INV2 Y(0 0) + P(0 8) + P(1 3) + P(2 2) + P(3 2) + P(4 6) + P(5 5) + ) + ) + X(TOP + R((-305 350) (15415 9225)) + N(1 + R(l10 (3200 4800) (8800 400)) + R(l10 (-5110 -425) (0 0)) + R(l10 (-4295 30) (0 0)) + R(l10 (9270 -80) (0 0)) + ) + N(2 + R(l10 (-305 4435) (250 1220)) + R(l10 (3665 -4655) (2780 400)) + R(l10 (-2780 6900) (2815 440)) + R(l10 (-710 -250) (0 0)) + R(l10 (3675 -165) (1975 565)) + R(l10 (-1975 -8190) (1975 575)) + R(l10 (-1005 -255) (0 0)) + ) + N(3 + R(l3 (12950 2130) (2160 250)) + R(l3 (-250 0) (250 4990)) + R(l3 (-1605 0) (1605 250)) + R(l7 (-1545 -250) (240 250)) + R(l8 (-560 -375) (690 510)) + ) + N(4 + R(l3 (12100 7300) (640 530)) + R(l7 (-540 -415) (270 250)) + R(l8 (-1695 -250) (1695 250)) + R(l8 (-4075 -5650) (2630 250)) + R(l8 (-250 0) (250 5150)) + ) + N(5 + R(l7 (6465 7325) (220 240)) + R(l8 (-4100 -5365) (3125 250)) + R(l8 (-250 0) (250 4860)) + R(l8 (-250 0) (1225 250)) + ) + N(6 I($1.A) + R(l11 (975 7530) (0 0)) + ) + N(7 I($1.Q) + R(l12 (13260 2010) (0 0)) + ) + X(2 INV2 Y(11365 375) + P(0 1) + P(1 4) + P(2 3) + P(3 3) + P(4 7) + P(5 2) + ) + X(3 INVCHAIN Y(610 365) + P(0 6) + P(1 5) + P(2 2) + P(3 5) + P(4 4) + P(5 1) + ) + ) +) +H( + K(PMOS MOS3) + K(NMOS MOS3) + X(INV + N(1 I(VDD)) + N(2 I(VSS)) + N(3 I(A)) + N(4 I(Q)) + P(1 I(VDD)) + P(2 I(VSS)) + P(3 I(A)) + P(4 I(Q)) + D(1 PMOS + I($1) + E(L 0.25) + E(W 0.95) + E(AS 0) + E(AD 0) + E(PS 0) + E(PD 0) + T(S 1) + T(G 3) + T(D 4) + ) + D(2 NMOS + I($3) + E(L 0.25) + E(W 0.95) + E(AS 0) + E(AD 0) + E(PS 0) + E(PD 0) + T(S 2) + T(G 3) + T(D 4) + ) + ) + X(INV2 + N(1 I(VDD)) + N(2 I(VSS)) + N(3 I(A1)) + N(4 I(Q1)) + N(5 I(A2)) + N(6 I(Q2)) + P(1 I(VDD)) + P(2 I(VSS)) + P(3 I(A1)) + P(4 I(Q1)) + P(5 I(A2)) + P(6 I(Q2)) + X(1 INV I($1) + P(0 1) + P(1 2) + P(2 3) + P(3 4) + ) + X(2 INV I($2) + P(0 1) + P(1 2) + P(2 5) + P(3 6) + ) + ) + X(INVCHAIN + N(1 I(VDD)) + N(2 I(VSS)) + N(3 I(A1)) + N(4 I(Q1)) + N(5 I(A2)) + N(6 I(Q2)) + N(7 I('1')) + N(8 I('2')) + P(1 I(VDD)) + P(2 I(VSS)) + P(3 I(A1)) + P(4 I(Q1)) + P(5 I(A2)) + P(6 I(Q2)) + X(1 INV2 I($2) + P(0 1) + P(1 2) + P(2 3) + P(3 7) + P(4 7) + P(5 4) + ) + X(2 INV2 I($3) + P(0 1) + P(1 2) + P(2 5) + P(3 8) + P(4 8) + P(5 6) + ) + ) + X(TOP + N(1 I(VDD)) + N(2 I(VSS)) + N(3 I(A)) + N(4 I('1')) + N(5 I('3')) + N(6 I('2')) + N(7 I(Q)) + X(1 INVCHAIN I($1) + P(0 1) + P(1 2) + P(2 3) + P(3 4) + P(4 4) + P(5 5) + ) + X(2 INV2 I($2) + P(0 1) + P(1 2) + P(2 5) + P(3 6) + P(4 6) + P(5 7) + ) + ) +) +Z( + X(INV INV 1 + Z( + N(3 3 1) + N(4 4 1) + N(2 1 1) + N(1 2 1) + P(2 2 1) + P(3 3 1) + P(1 0 1) + P(0 1 1) + D(2 2 1) + D(1 1 1) + ) + ) + X(INV2 INV2 1 + Z( + N(2 3 1) + N(3 5 1) + N(4 4 1) + N(5 6 1) + N(1 1 1) + N(6 2 1) + P(1 2 1) + P(2 4 1) + P(3 3 1) + P(4 5 1) + P(0 0 1) + P(5 1 1) + X(2 1 1) + X(1 2 1) + ) + ) + X(INVCHAIN INVCHAIN 1 + L( + M(W B('Matching nets OUT vs. Q1 from an ambiguous group of nets')) + M(W B('Matching nets OUT2 vs. Q2 from an ambiguous group of nets')) + M(I B('Matching nets $2 vs. 1 following an ambiguous match')) + M(I B('Matching nets IN vs. A1 following an ambiguous match')) + M(I B('Matching nets $1 vs. 2 following an ambiguous match')) + M(I B('Matching nets IN2 vs. A2 following an ambiguous match')) + ) + Z( + N(2 7 1) + N(1 8 1) + N(3 3 1) + N(4 5 1) + N(6 4 W) + N(7 6 W) + N(8 1 1) + N(5 2 1) + P(0 2 1) + P(1 4 1) + P(3 3 1) + P(4 5 1) + P(5 0 1) + P(2 1 1) + X(2 1 1) + X(1 2 1) + ) + ) + X(TOP TOP 1 + Z( + N(5 4 1) + N(3 6 1) + N(4 5 1) + N(1 1 1) + N(2 2 1) + N(6 3 1) + N(7 7 1) + X(2 2 1) + X(3 1 1) + ) + ) +) diff --git a/testdata/lvs/must_connect3.sch b/testdata/lvs/must_connect3.sch new file mode 100644 index 000000000..9114c6de9 --- /dev/null +++ b/testdata/lvs/must_connect3.sch @@ -0,0 +1,24 @@ + +.SUBCKT TOP +X$1 VDD VSS A 1 1 3 INVCHAIN +X$2 VDD VSS 3 2 2 Q INV2 +.ENDS TOP + +* cell INVCHAIN +.SUBCKT INVCHAIN VDD VSS A1 Q1 A2 Q2 +X$2 VDD VSS A1 1 1 Q1 INV2 +X$3 VDD VSS A2 2 2 Q2 INV2 +.ENDS INVCHAIN + +* cell INV2 +.SUBCKT INV2 VDD VSS A1 Q1 A2 Q2 +X$1 VDD VSS A1 Q1 INV +X$2 VDD VSS A2 Q2 INV +.ENDS INV2 + +* cell INV +.SUBCKT INV VDD VSS A Q +M$1 VDD A Q VDD PMOS L=0.25U W=0.95U +M$3 VSS A Q VSS NMOS L=0.25U W=0.95U +.ENDS INV + From ffffe7327c349e4cb92201e6bae1df1a69191777 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 Mar 2024 01:10:08 +0100 Subject: [PATCH 48/63] Preparing for merge with master --- src/drc/unit_tests/drcSimpleTests.cc | 24 +++++++++--------- ...pleTests_90.drc => drcSimpleTests_100.drc} | 0 ...pleTests_90.gds => drcSimpleTests_100.gds} | Bin ...pleTests_91.drc => drcSimpleTests_101.drc} | 0 ...pleTests_91.gds => drcSimpleTests_101.gds} | Bin ...pleTests_92.drc => drcSimpleTests_102.drc} | 0 ...pleTests_92.gds => drcSimpleTests_102.gds} | Bin ...ests_au90.gds => drcSimpleTests_au100.gds} | Bin ...ts_au90d.gds => drcSimpleTests_au100d.gds} | Bin ...ests_au91.gds => drcSimpleTests_au101.gds} | Bin ...ts_au91d.gds => drcSimpleTests_au101d.gds} | Bin ...ests_au92.gds => drcSimpleTests_au102.gds} | Bin ...ts_au92d.gds => drcSimpleTests_au102d.gds} | Bin 13 files changed, 12 insertions(+), 12 deletions(-) rename testdata/drc/{drcSimpleTests_90.drc => drcSimpleTests_100.drc} (100%) rename testdata/drc/{drcSimpleTests_90.gds => drcSimpleTests_100.gds} (100%) rename testdata/drc/{drcSimpleTests_91.drc => drcSimpleTests_101.drc} (100%) rename testdata/drc/{drcSimpleTests_91.gds => drcSimpleTests_101.gds} (100%) rename testdata/drc/{drcSimpleTests_92.drc => drcSimpleTests_102.drc} (100%) rename testdata/drc/{drcSimpleTests_92.gds => drcSimpleTests_102.gds} (100%) rename testdata/drc/{drcSimpleTests_au90.gds => drcSimpleTests_au100.gds} (100%) rename testdata/drc/{drcSimpleTests_au90d.gds => drcSimpleTests_au100d.gds} (100%) rename testdata/drc/{drcSimpleTests_au91.gds => drcSimpleTests_au101.gds} (100%) rename testdata/drc/{drcSimpleTests_au91d.gds => drcSimpleTests_au101d.gds} (100%) rename testdata/drc/{drcSimpleTests_au92.gds => drcSimpleTests_au102.gds} (100%) rename testdata/drc/{drcSimpleTests_au92d.gds => drcSimpleTests_au102d.gds} (100%) diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index 64e8b84ec..690f2b644 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -1617,32 +1617,32 @@ TEST(89_deep_with_mag_cop_size_aniso) run_test (_this, "89", true); } -TEST(90_edge_interaction_with_count) +TEST(100_edge_interaction_with_count) { - run_test (_this, "90", false); + run_test (_this, "100", false); } -TEST(90d_edge_interaction_with_count) +TEST(100d_edge_interaction_with_count) { - run_test (_this, "90", true); + run_test (_this, "100", true); } -TEST(91_edge_booleans_with_dots) +TEST(101_edge_booleans_with_dots) { - run_test (_this, "91", false); + run_test (_this, "101", false); } -TEST(91d_edge_booleans_with_dots) +TEST(101d_edge_booleans_with_dots) { - run_test (_this, "91", true); + run_test (_this, "101", true); } -TEST(92_edge_modes) +TEST(102_edge_modes) { - run_test (_this, "92", false); + run_test (_this, "102", false); } -TEST(92d_edge_modes) +TEST(102d_edge_modes) { - run_test (_this, "92", true); + run_test (_this, "102", true); } diff --git a/testdata/drc/drcSimpleTests_90.drc b/testdata/drc/drcSimpleTests_100.drc similarity index 100% rename from testdata/drc/drcSimpleTests_90.drc rename to testdata/drc/drcSimpleTests_100.drc diff --git a/testdata/drc/drcSimpleTests_90.gds b/testdata/drc/drcSimpleTests_100.gds similarity index 100% rename from testdata/drc/drcSimpleTests_90.gds rename to testdata/drc/drcSimpleTests_100.gds diff --git a/testdata/drc/drcSimpleTests_91.drc b/testdata/drc/drcSimpleTests_101.drc similarity index 100% rename from testdata/drc/drcSimpleTests_91.drc rename to testdata/drc/drcSimpleTests_101.drc diff --git a/testdata/drc/drcSimpleTests_91.gds b/testdata/drc/drcSimpleTests_101.gds similarity index 100% rename from testdata/drc/drcSimpleTests_91.gds rename to testdata/drc/drcSimpleTests_101.gds diff --git a/testdata/drc/drcSimpleTests_92.drc b/testdata/drc/drcSimpleTests_102.drc similarity index 100% rename from testdata/drc/drcSimpleTests_92.drc rename to testdata/drc/drcSimpleTests_102.drc diff --git a/testdata/drc/drcSimpleTests_92.gds b/testdata/drc/drcSimpleTests_102.gds similarity index 100% rename from testdata/drc/drcSimpleTests_92.gds rename to testdata/drc/drcSimpleTests_102.gds diff --git a/testdata/drc/drcSimpleTests_au90.gds b/testdata/drc/drcSimpleTests_au100.gds similarity index 100% rename from testdata/drc/drcSimpleTests_au90.gds rename to testdata/drc/drcSimpleTests_au100.gds diff --git a/testdata/drc/drcSimpleTests_au90d.gds b/testdata/drc/drcSimpleTests_au100d.gds similarity index 100% rename from testdata/drc/drcSimpleTests_au90d.gds rename to testdata/drc/drcSimpleTests_au100d.gds diff --git a/testdata/drc/drcSimpleTests_au91.gds b/testdata/drc/drcSimpleTests_au101.gds similarity index 100% rename from testdata/drc/drcSimpleTests_au91.gds rename to testdata/drc/drcSimpleTests_au101.gds diff --git a/testdata/drc/drcSimpleTests_au91d.gds b/testdata/drc/drcSimpleTests_au101d.gds similarity index 100% rename from testdata/drc/drcSimpleTests_au91d.gds rename to testdata/drc/drcSimpleTests_au101d.gds diff --git a/testdata/drc/drcSimpleTests_au92.gds b/testdata/drc/drcSimpleTests_au102.gds similarity index 100% rename from testdata/drc/drcSimpleTests_au92.gds rename to testdata/drc/drcSimpleTests_au102.gds diff --git a/testdata/drc/drcSimpleTests_au92d.gds b/testdata/drc/drcSimpleTests_au102d.gds similarity index 100% rename from testdata/drc/drcSimpleTests_au92d.gds rename to testdata/drc/drcSimpleTests_au102d.gds From d906f870b0f5b6ff9c0769d7504dc07e9c3ec61c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Feb 2024 22:37:39 +0100 Subject: [PATCH 49/63] Warning level was ignored for some warnings in LEF/DEF reader --- .../lefdef/db_plugin/dbLEFDEFImporter.cc | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc index 5f0394057..803cca456 100644 --- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc +++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc @@ -1117,7 +1117,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map::const_iterator l = layers.begin (); l != layers.end (); ++l) { @@ -1176,7 +1176,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::mapsecond == ViaGeometry) { @@ -1284,7 +1284,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::mapsecond == All) { @@ -1364,21 +1364,22 @@ LEFDEFReaderState::open_layer (db::Layout &layout, const std::string &n, LayerPu m_layers.insert (std::make_pair (std::make_pair (n, LayerDetailsKey (purpose, mask)), ll)); if (ll.empty () && ! has_fallback (purpose)) { + std::string msg; if (n.empty ()) { - tl::warn << tl::to_string (tr ("No mapping for purpose")) << " '" << purpose_to_name (purpose) << "'" << tl::noendl; + msg = tl::to_string (tr ("No mapping for purpose")) + " '" + purpose_to_name (purpose) + "'"; } else { - tl::warn << tl::to_string (tr ("No mapping for layer")) << " '" << n << "', purpose '" << purpose_to_name (purpose) << "'" << tl::noendl; + msg = tl::to_string (tr ("No mapping for layer")) + " '" + n + "', purpose '" + purpose_to_name (purpose) + "'"; } if (mask > 0) { - tl::warn << tl::to_string (tr (" Mask ")) << mask << tl::noendl; + msg += tl::to_string (tr (" Mask ")) + tl::to_string (mask); } // not printing via size - too confusing? #if 0 if (via_size != db::DVector ()) { - tl::warn << tl::to_string (tr (" Via size ")) << via_size.to_string () << tl::noendl; + msg += tl::to_string (tr (" Via size ")) + via_size.to_string (); } #endif - tl::warn << tl::to_string (tr (" - layer is ignored")); + common_reader_warn (msg + tl::to_string (tr (" - layer is ignored"))); } return ll; From 8886c152bec35d891695d49813e4ecc35e67a999 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 Mar 2024 09:35:24 +0100 Subject: [PATCH 50/63] Changing location of test file so we don't spoil WebDAV tests from previous versions --- src/tl/unit_tests/tlHttpStreamTests.cc | 2 +- src/tl/unit_tests/tlWebDAVTests.cc | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tl/unit_tests/tlHttpStreamTests.cc b/src/tl/unit_tests/tlHttpStreamTests.cc index 4acf10f3c..203ff8e1e 100644 --- a/src/tl/unit_tests/tlHttpStreamTests.cc +++ b/src/tl/unit_tests/tlHttpStreamTests.cc @@ -27,7 +27,7 @@ #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_url1_gz ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata2/text.gz"); static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1"); TEST(1) diff --git a/src/tl/unit_tests/tlWebDAVTests.cc b/src/tl/unit_tests/tlWebDAVTests.cc index 67598d832..6d2948e7f 100644 --- a/src/tl/unit_tests/tlWebDAVTests.cc +++ b/src/tl/unit_tests/tlWebDAVTests.cc @@ -70,7 +70,6 @@ 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 408a30164e96c2de78c93acc264eee522c32e185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6fferlein?= Date: Sat, 9 Mar 2024 09:36:08 +0100 Subject: [PATCH 51/63] Maybe fixing weird menu texts (issue #1631) (#1634) Co-authored-by: Matthias Koefferlein --- src/rba/rba/rba.cc | 2 +- src/rba/rba/rbaUtils.cc | 4 ++-- src/rba/rba/rbaUtils.h | 8 -------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/rba/rba/rba.cc b/src/rba/rba/rba.cc index a4df7f04b..8bd642f04 100644 --- a/src/rba/rba/rba.cc +++ b/src/rba/rba/rba.cc @@ -2358,7 +2358,7 @@ RubyInterpreter::load_file (const std::string &filename_utf8) { std::string fl (rb_cstring_from_utf8 (filename_utf8)); - rb_set_progname (rb_str_new (fl.c_str (), long (fl.size ()))); + ruby_script (fl.c_str ()); rb_set_errinfo (Qnil); int error = 0; diff --git a/src/rba/rba/rbaUtils.cc b/src/rba/rba/rbaUtils.cc index ab9383ba9..f11463965 100644 --- a/src/rba/rba/rbaUtils.cc +++ b/src/rba/rba/rbaUtils.cc @@ -530,10 +530,10 @@ VALUE rba_eval_string_in_context (const char *expr, const char *file, int line, rb_set_errinfo (Qnil); if (file) { - rb_set_progname (rb_str_new (file, long (strlen (file)))); + ruby_script (file); } else { const char *e = ""; - rb_set_progname (rb_str_new (e, long (strlen (e)))); + ruby_script (e); } int argc; diff --git a/src/rba/rba/rbaUtils.h b/src/rba/rba/rbaUtils.h index 3105f1dfc..1f9e6b5d8 100644 --- a/src/rba/rba/rbaUtils.h +++ b/src/rba/rba/rbaUtils.h @@ -147,14 +147,6 @@ inline std::string rb_cstring_from_utf8 (const std::string &utf8) return utf8; } -/** - * @brief A setter for $0 - */ -inline void rb_set_progname (VALUE pn) -{ - rb_gv_set ("PROGRAM_NAME", pn); -} - /** * @brief Sets up a block for protected evaluation * From d60583a9b46debcb25f03dcba5a6f7965e6b085d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 Mar 2024 18:46:45 +0100 Subject: [PATCH 52/63] Robustness of tests --- src/db/unit_tests/dbDeepEdgesTests.cc | 64 +++++++++++++-------------- src/db/unit_tests/dbEdgesTests.cc | 30 ++++++------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/db/unit_tests/dbDeepEdgesTests.cc b/src/db/unit_tests/dbDeepEdgesTests.cc index 3dc27c43c..7d992997e 100644 --- a/src/db/unit_tests/dbDeepEdgesTests.cc +++ b/src/db/unit_tests/dbDeepEdgesTests.cc @@ -1454,55 +1454,55 @@ TEST(22_InteractingWithCount) db::Edges edup; - EXPECT_EQ (e.selected_interacting (e2).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (2)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (2), size_t(2)).to_string (), "(0,10;200,10)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (2), size_t(3)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (3)).to_string (), "(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (4)).to_string (), ""); + EXPECT_EQ (db::compare (e.selected_interacting (e2), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (2)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (2), size_t(2)), "(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (2), size_t(3)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (3)), "(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (4)), ""), true); edup = e; edup.select_interacting (e2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (edup, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); - EXPECT_EQ (e.selected_not_interacting (e2).to_string (), ""); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (2)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (2), size_t(2)).to_string (), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (2), size_t(3)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (3)).to_string (), "(0,0;200,0);(0,10;200,10)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (4)).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2), ""), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (2)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (2), size_t(2)), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (2), size_t(3)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (3)), "(0,0;200,0);(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (4)), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); edup = e; edup.select_not_interacting (e2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (edup, "(0,0;200,0)"), true); - EXPECT_EQ (e.selected_interacting_differential (e2, size_t (2), size_t(3)).first.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting_differential (e2, size_t (2), size_t(3)).second.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (e.selected_interacting_differential (e2, size_t (2), size_t(3)).first, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting_differential (e2, size_t (2), size_t(3)).second, "(0,0;200,0)"), true); - EXPECT_EQ (e.selected_interacting (r2).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (2)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (2), size_t(2)).to_string (), "(0,10;200,10)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (2), size_t(3)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (3)).to_string (), "(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (4)).to_string (), ""); + EXPECT_EQ (db::compare (e.selected_interacting (r2), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (2)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (2), size_t(2)), "(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (2), size_t(3)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (3)), "(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (4)), ""), true); edup = e; edup.select_interacting (r2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (edup, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); - EXPECT_EQ (e.selected_not_interacting (r2).to_string (), ""); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (2)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (2), size_t(2)).to_string (), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (2), size_t(3)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (3)).to_string (), "(0,0;200,0);(0,10;200,10)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (4)).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2), ""), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (2)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (2), size_t(2)), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (2), size_t(3)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (3)), "(0,0;200,0);(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (4)), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); edup = e; edup.select_not_interacting (r2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (edup, "(0,0;200,0)"), true); - EXPECT_EQ (e.selected_interacting_differential (r2, size_t (2), size_t(3)).first.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting_differential (r2, size_t (2), size_t(3)).second.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (e.selected_interacting_differential (r2, size_t (2), size_t(3)).first, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting_differential (r2, size_t (2), size_t(3)).second, "(0,0;200,0)"), true); } diff --git a/src/db/unit_tests/dbEdgesTests.cc b/src/db/unit_tests/dbEdgesTests.cc index 727405280..97f0d890c 100644 --- a/src/db/unit_tests/dbEdgesTests.cc +++ b/src/db/unit_tests/dbEdgesTests.cc @@ -789,27 +789,27 @@ TEST(20) EXPECT_EQ (r2.has_valid_edges (), false); db::Region rr1 (db::RecursiveShapeIterator (ly, ly.cell (top), lp1), db::ICplxTrans (), false); EXPECT_EQ (rr1.has_valid_polygons (), false); - EXPECT_EQ ((r1 & r2).to_string (100), "(80,70;80,40)"); - EXPECT_EQ ((r1 + r2).to_string (100), "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(50,70;80,70);(80,70;80,40);(80,40;50,40);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(80,40;80,70);(80,70;110,70);(110,70;110,40);(110,40;80,40);(110,40;110,70);(110,70;140,70);(140,70;140,40);(140,40;110,40)"); - EXPECT_EQ ((r1 + r2).merged ().to_string (100), "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(50,70;140,70);(140,70;140,40);(140,40;50,40)"); - EXPECT_EQ ((r1 | r2).to_string (100), "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(50,70;140,70);(140,70;140,40);(140,40;50,40)"); - EXPECT_EQ ((r1 ^ r2).to_string (100), "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(50,70;140,70);(140,70;140,40);(140,40;50,40)"); - EXPECT_EQ ((r1 ^ r1).to_string (100), ""); - EXPECT_EQ ((r1 - r2).to_string (100), "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(50,70;80,70);(80,40;50,40)"); - EXPECT_EQ ((r1 - r1).to_string (100), ""); - EXPECT_EQ (r2.merged ().to_string (100), "(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(80,40;80,70);(80,70;140,70);(140,70;140,40);(140,40;80,40)"); - EXPECT_EQ (rr1.to_string (100), "(0,0;0,30;30,30;30,0);(50,0;50,30;80,30;80,0);(50,40;50,70;80,70;80,40)"); - EXPECT_EQ (r2.selected_interacting (rr1).to_string (100), "(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(40,10;10,10);(80,40;80,70);(80,70;140,70);(140,40;80,40)"); - EXPECT_EQ (r2.selected_interacting_differential (rr1).first.to_string (100), "(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(40,10;10,10);(80,40;80,70);(80,70;140,70);(140,40;80,40)"); - EXPECT_EQ (r2.selected_not_interacting (rr1).to_string (100), "(10,40;40,40);(40,40;40,10);(140,70;140,40)"); - EXPECT_EQ (r2.selected_interacting_differential (rr1).second.to_string (100), "(10,40;40,40);(40,40;40,10);(140,70;140,40)"); + EXPECT_EQ (db::compare (r1 & r2, "(80,70;80,40)"), true); + EXPECT_EQ (db::compare (r1 + r2, "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(50,70;80,70);(80,70;80,40);(80,40;50,40);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(80,40;80,70);(80,70;110,70);(110,70;110,40);(110,40;80,40);(110,40;110,70);(110,70;140,70);(140,70;140,40);(140,40;110,40)"), true); + EXPECT_EQ (db::compare ((r1 + r2).merged (), "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(50,70;140,70);(140,70;140,40);(140,40;50,40)"), true); + EXPECT_EQ (db::compare (r1 | r2, "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(50,70;140,70);(140,70;140,40);(140,40;50,40)"), true); + EXPECT_EQ (db::compare (r1 ^ r2, "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(50,70;140,70);(140,70;140,40);(140,40;50,40)"), true); + EXPECT_EQ (db::compare (r1 ^ r1, ""), true); + EXPECT_EQ (db::compare (r1 - r2, "(0,0;0,30);(0,30;30,30);(30,30;30,0);(30,0;0,0);(50,0;50,30);(50,30;80,30);(80,30;80,0);(80,0;50,0);(50,40;50,70);(50,70;80,70);(80,40;50,40)"), true); + EXPECT_EQ (db::compare (r1 - r1, ""), true); + EXPECT_EQ (db::compare (r2.merged (), "(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(10,40;40,40);(40,40;40,10);(40,10;10,10);(80,40;80,70);(80,70;140,70);(140,70;140,40);(140,40;80,40)"), true); + EXPECT_EQ (db::compare (rr1, "(0,0;0,30;30,30;30,0);(50,0;50,30;80,30;80,0);(50,40;50,70;80,70;80,40)"), true); + EXPECT_EQ (db::compare (r2.selected_interacting (rr1), "(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(40,10;10,10);(80,40;80,70);(80,70;140,70);(140,40;80,40)"), true); + EXPECT_EQ (db::compare (r2.selected_interacting_differential (rr1).first, "(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(40,10;10,10);(80,40;80,70);(80,70;140,70);(140,40;80,40)"), true); + EXPECT_EQ (db::compare (r2.selected_not_interacting (rr1), "(10,40;40,40);(40,40;40,10);(140,70;140,40)"), true); + EXPECT_EQ (db::compare (r2.selected_interacting_differential (rr1).second, "(10,40;40,40);(40,40;40,10);(140,70;140,40)"), true); db::Edges r2dup = r2; r2.select_interacting (rr1); EXPECT_EQ (db::compare (r2, "(60,10;60,20);(60,20;70,20);(70,20;70,10);(70,10;60,10);(10,10;10,40);(40,10;10,10);(80,40;80,70);(80,70;140,70);(140,40;80,40)"), true); r2 = r2dup; r2.select_not_interacting (rr1); - EXPECT_EQ (r2.to_string (100), "(10,40;40,40);(40,40;40,10);(140,70;140,40)"); + EXPECT_EQ (db::compare (r2, "(10,40;40,40);(40,40;40,10);(140,70;140,40)"), true); r2 = db::Edges (db::RecursiveShapeIterator (ly, ly.cell (top), l2), false); EXPECT_EQ (r2.has_valid_edges (), false); From c134b6c55cfc320c96b04482a962a153d4ecef53 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 Mar 2024 18:47:13 +0100 Subject: [PATCH 53/63] Update of test data needed, because OASIS layer names are present now even if there is no shape --- testdata/lefdef/blend_mode/au1.oas.gz | Bin 630 -> 531 bytes testdata/lefdef/blend_mode/au2.oas.gz | Bin 610 -> 503 bytes testdata/lefdef/blend_mode/au3.oas.gz | Bin 607 -> 500 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/testdata/lefdef/blend_mode/au1.oas.gz b/testdata/lefdef/blend_mode/au1.oas.gz index e0e95c3a56e40b4127e3ef8b83e69be54b9ab0ea..a1115551b6b1cecfbb2adca5e326afabda1514f5 100644 GIT binary patch literal 531 zcmY!lcJ=kt^>+;R4CduxWH!_@V0gjKC?n3q!6L)YEF;ds&mben(Z>XUoMnybM; zfb~F;;|1o9>4KX(8~>b$4qRRSKbqrK6n{_8gJqf+!3PTDn6Noy&$jcyjfKf)Bk%N&REb@Y#8Dyi3I0MH$MrM$`Am({S eW{7h@oNJ6sj2{>o85q?W%NaX>jE0c~7#IM2=-d|o literal 630 zcmY!lcJ=kt^>+;R4CduxWH!_@V0gjKfDB|rrGn#q9V6m{J>C6WUE)3cLR{TlgW|(I zT|zuKSY&u*Akv|J*c8Z!as|hS_y@#0yZZR>uojmlsTj%#@-Ua=7qIgM$Gf`(#|H%Y z2e<}>L~=3%Wtl-T%wQHoF*mz^Xo!!epDS|;GXoDYhg;a!P|wdbL_fgOPv6bc$46gJ zPmh@gs0x<^P=^tw4nD9B>=N9fVV;gyY~cm!!7athC~`Q3A%&L-M4e#dWe_~TD5K86 S!N|`55_!SS)G(3(0|NlH-_Rof diff --git a/testdata/lefdef/blend_mode/au2.oas.gz b/testdata/lefdef/blend_mode/au2.oas.gz index b05825a58ce1a8deb53006e0634912275e5c8bee..091ede9a861f2450c28b08c9bf6fecc7ec4a800c 100644 GIT binary patch delta 270 zcmaFF@|}5tXg#BhI1>kp3_r7sI14|65<{v_?ip*Y1_J@s14WJ(m^-EmZtiUSb0Ru$ zb@~5jj$2XuJv|SWX>P3IH=TLl!GUYpm-DutNr=-|YFpH|aR%F4o5h=Br=9Qnw3<0% z>)xNtoXjQp1)R*qrAbN*yBM7wr+w$={rK@qajIWp+5^XD1>N;Oq+b2p+4h3H;@3-d z_J)Y6s;V+;R4CduxWH!_@V0gjKfDB|rrGn#q9V6m{J>C6WUE)3cLR{TlgW|(I zT|zuKSY&u*Akv|J*c8Z!as|hS_y@#0yZZR>Fqh;Pu=54SySoI(2L$;CxCVtpaxxc} zCNYC#m_aOVcK^^2A5TA5<`iZI9%K%;u&<$>pKFMIfTy3no2QSDzMh^QGY?P|E(xFx zBTOB9U>(>cxJAP}9kJNL3)F*KikDI3a0kp3_r7sI14|65<{v_?ip*Y1_J@s14WJ(m^-EmZtiUSb0Ru$ zb@~5jj$2XuJv|SWX>P3IH=TLl!GUYpm-DutNr=-|YFpH|aR%F4o5h=Br=9Qnw3<0% z>)xNtoXjQp1)R*qrAbN*yBM7wr+w$={rK@qajIWp+5^XD1>Jc+q+b2p+4h3H;@3-d z_J)Y6s;VGJisWU y&d9;Y4;FdB&J40kMx24;3?nnhMiBD|BNO92Mn(okWyWI0T1JM+iHskZ7#IL}wO+jd literal 607 zcmY!lcJ=kt^>+;R4CduxWH!_@V0gjKfDB|rrGn#q9V6m{J>C6WUE)3cLR{TlgW|(I zT|zuKSY&u*Akv|J*c8Z!as|hS_y@#0yZZR>Fqh;Pu=54SySoI(2L$;CxCVtpaxxc} zCNYC#m_aOVcK^^2A5TA5<`iZI9%K%;u&<$>pKFMIfTy3no2QSDzMh^QGY?P|E(xFx zBTOB9U>(>cxJAP}9kJNL3)F*KikDI3a0=L73?Pvg>`V Date: Mon, 11 Mar 2024 22:42:33 +0100 Subject: [PATCH 54/63] [consider merging] Bugfix: connect_explicit did not accept an array of nets as single argument --- src/drc/drc/built-in-macros/_drc_netter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/drc/drc/built-in-macros/_drc_netter.rb b/src/drc/drc/built-in-macros/_drc_netter.rb index 1985cc67c..8c970483c 100644 --- a/src/drc/drc/built-in-macros/_drc_netter.rb +++ b/src/drc/drc/built-in-macros/_drc_netter.rb @@ -379,7 +379,8 @@ module DRC arg1.is_a?(String) || raise("The first argument has to be a string") @pre_extract_config << lambda { |l2n| l2n.join_nets(arg1, arg2) } else - arg1.is_a?(String) || raise("The argument has to be a string") + arg1.is_a?(Array) || raise("The argument has to be an array of strings") + arg1.find { |a| !a.is_a?(String) } && raise("The argument has to be an array of strings") @pre_extract_config << lambda { |l2n| l2n.join_nets(arg1) } end From fa14afbbf30ba287e647cd684b38952efb08f43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6fferlein?= Date: Wed, 13 Mar 2024 21:50:48 +0100 Subject: [PATCH 55/63] Pcell limits (#1654) * Klayout PyCell integration -added tl::optional as derivate of std::optional for c++17 and above, reduced implementation otherwise -fixed missing include for c++17 and above -added range constraints for PCell parameter Signed-off-by: ThomasZecha * tl::optional now based on internal implementation, added tests and tl::to_string binding * Refactoring the range into min_value and max_value attributes without action and resolution. * Integration of feature into PCell framework * Cleanup and fixed some compile issues * Cleanup, added tests * [consider merging] Added pymod distutil_src files to dependencies. * Updated Python stubs * User feedback: upon entering an invalid value string into an edit box, do not reset the field --------- Signed-off-by: ThomasZecha Co-authored-by: ThomasZecha Co-authored-by: Matthias Koefferlein --- .../pcell_declaration_helper.lym | 10 + .../pcell_declaration_helper.lym | 6 + src/db/db/dbPCellDeclaration.h | 56 +- src/db/db/gsiDeclDbLibrary.cc | 84 +- src/edt/edt/edtPCellParametersPage.cc | 37 +- src/edt/edt/edtPCellParametersPage.h | 1 + .../klayout/db/pcell_declaration_helper.py | 6 +- src/pymod/distutils_src/klayout/dbcore.pyi | 1129 +++++++++++------ src/pymod/distutils_src/klayout/laycore.pyi | 64 +- src/pymod/distutils_src/klayout/tlcore.pyi | 27 +- src/pymod/pymod.pri | 2 + src/tl/tl/tl.pro | 6 +- src/tl/tl/tlOptional.cc | 30 + src/tl/tl/tlOptional.h | 156 +++ src/tl/unit_tests/tlOptionalTests.cc | 97 ++ src/tl/unit_tests/unit_tests.pro | 1 + testdata/ruby/dbPCells.rb | 72 ++ 17 files changed, 1330 insertions(+), 454 deletions(-) create mode 100644 src/tl/tl/tlOptional.cc create mode 100644 src/tl/tl/tlOptional.h create mode 100644 src/tl/unit_tests/tlOptionalTests.cc diff --git a/src/db/db/built-in-macros/pcell_declaration_helper.lym b/src/db/db/built-in-macros/pcell_declaration_helper.lym index 43754b65d..ba97fac7d 100644 --- a/src/db/db/built-in-macros/pcell_declaration_helper.lym +++ b/src/db/db/built-in-macros/pcell_declaration_helper.lym @@ -118,6 +118,12 @@ Optional, named parameters are @li @b:unit@/b: the unit string @/li + @li + @b:min_value@/b: the minimum value (effective for numerical types and if no choices are present) + @/li + @li + @b:max_value@/b: the maximum value (effective for numerical types and if no choices are present) + @/li @li @b:default@/b: the default value @/li @@ -335,6 +341,8 @@ module RBA # :hidden -> (boolean) true, if the parameter is not shown in the dialog # :readonly -> (boolean) true, if the parameter cannot be edited # :unit -> the unit string + # :min_value -> the minimum value (only effective for numerical types and if no choices are present) + # :max_value -> the maximum value (only effective for numerical types and if no choices are present) # :default -> the default value # :choices -> ([ [ d, v ], ...) choice descriptions/value for choice type # this method defines accessor methods for the parameters @@ -373,6 +381,8 @@ module RBA args[:hidden] && pdecl.hidden = args[:hidden] args[:readonly] && pdecl.readonly = args[:readonly] args[:unit] && pdecl.unit = args[:unit] + args[:min_value] && pdecl.min_value = args[:min_value] + args[:max_value] && pdecl.max_value = args[:max_value] if args[:choices] if !args[:choices].is_a?(Array) raise ":choices value must be an array of two-element arrays (description, value)" diff --git a/src/db/db/built-in-pymacros/pcell_declaration_helper.lym b/src/db/db/built-in-pymacros/pcell_declaration_helper.lym index 462883154..2b3113ca0 100644 --- a/src/db/db/built-in-pymacros/pcell_declaration_helper.lym +++ b/src/db/db/built-in-pymacros/pcell_declaration_helper.lym @@ -127,6 +127,12 @@ Optional, named parameters are @li @bunit@/b: the unit string @/li + @li + @bmin_value@/b: the minimum value (effective for numerical types and if no choices are present) + @/li + @li + @bmax_value@/b: the maximum value (effective for numerical types and if no choices are present) + @/li @li @bdefault@/b: the default value @/li diff --git a/src/db/db/dbPCellDeclaration.h b/src/db/db/dbPCellDeclaration.h index 08e058683..3687ef71a 100644 --- a/src/db/db/dbPCellDeclaration.h +++ b/src/db/db/dbPCellDeclaration.h @@ -30,6 +30,7 @@ #include "dbLayout.h" #include "tlVariant.h" #include "tlObject.h" +#include "tlOptional.h" namespace db { @@ -267,6 +268,56 @@ public: m_choice_descriptions = choice_descriptions; } + /** + * @brief Sets the minimum value + * + * The minimum value is a visual feature and limits the allowed values for numerical + * entry boxes. This applies to parameters of type int or double. The minimum value + * is not effective if choices are present. + * + * The minimum value is not enforced - for example there is no restriction implemented + * when setting values programmatically. + * + * Setting this attribute to "nil" (the default) implies "no limit". + */ + void set_min_value (const tl::Variant &min) + { + m_min_value = min; + } + + /** + * @brief Gets the minimum value (see \set_min_value) + */ + const tl::Variant &min_value () const + { + return m_min_value; + } + + /** + * @brief Sets the maximum value + * + * The maximum value is a visual feature and limits the allowed values for numerical + * entry boxes. This applies to parameters of type int or double. The maximum value + * is not effective if choices are present. + * + * The maximum value is not enforced - for example there is no restriction implemented + * when setting values programmatically. + * + * Setting this attribute to "nil" (the default) implies "no limit". + */ + void set_max_value (const tl::Variant &max) + { + m_max_value = max; + } + + /** + * @brief Gets the maximum value (see \set_max_value) + */ + const tl::Variant &max_value () const + { + return m_max_value; + } + /** * @brief Equality */ @@ -280,7 +331,9 @@ public: m_type == d.m_type && m_name == d.m_name && m_description == d.m_description && - m_unit == d.m_unit; + m_unit == d.m_unit && + m_min_value == d.m_min_value && + m_max_value == d.m_max_value; } private: @@ -291,6 +344,7 @@ private: type m_type; std::string m_name; std::string m_description, m_unit; + tl::Variant m_min_value, m_max_value; }; /** diff --git a/src/db/db/gsiDeclDbLibrary.cc b/src/db/db/gsiDeclDbLibrary.cc index 8f739a5ee..8fd578392 100644 --- a/src/db/db/gsiDeclDbLibrary.cc +++ b/src/db/db/gsiDeclDbLibrary.cc @@ -29,6 +29,7 @@ #include "dbPCellDeclaration.h" #include "dbLibrary.h" #include "dbLibraryManager.h" +#include "tlLog.h" namespace gsi { @@ -701,23 +702,23 @@ Class decl_PCellDeclaration (decl_PCellDeclaration_Native, // --------------------------------------------------------------- // db::PCellParameterDeclaration binding -unsigned int get_type (const db::PCellParameterDeclaration *pd) +static unsigned int get_type (const db::PCellParameterDeclaration *pd) { return (unsigned int) pd->get_type (); } -void set_type (db::PCellParameterDeclaration *pd, unsigned int t) +static void set_type (db::PCellParameterDeclaration *pd, unsigned int t) { pd->set_type (db::PCellParameterDeclaration::type (t)); } -void clear_choices (db::PCellParameterDeclaration *pd) +static void clear_choices (db::PCellParameterDeclaration *pd) { pd->set_choices (std::vector ()); pd->set_choice_descriptions (std::vector ()); } -void add_choice (db::PCellParameterDeclaration *pd, const std::string &d, const tl::Variant &v) +static void add_choice (db::PCellParameterDeclaration *pd, const std::string &d, const tl::Variant &v) { std::vector vv = pd->get_choices (); std::vector dd = pd->get_choice_descriptions (); @@ -772,26 +773,7 @@ static unsigned int pd_type_none () return (unsigned int) db::PCellParameterDeclaration::t_none; } -db::PCellParameterDeclaration *ctor_pcell_parameter (const std::string &name, unsigned int type, const std::string &description) -{ - db::PCellParameterDeclaration *pd = new db::PCellParameterDeclaration (); - pd->set_name (name); - pd->set_type (db::PCellParameterDeclaration::type (type)); - pd->set_description (description); - return pd; -} - -db::PCellParameterDeclaration *ctor_pcell_parameter_2 (const std::string &name, unsigned int type, const std::string &description, const tl::Variant &def) -{ - db::PCellParameterDeclaration *pd = new db::PCellParameterDeclaration (); - pd->set_name (name); - pd->set_type (db::PCellParameterDeclaration::type (type)); - pd->set_description (description); - pd->set_default (def); - return pd; -} - -db::PCellParameterDeclaration *ctor_pcell_parameter_3 (const std::string &name, unsigned int type, const std::string &description, const tl::Variant &def, const std::string &unit) +db::PCellParameterDeclaration *ctor_pcell_parameter (const std::string &name, unsigned int type, const std::string &description, const tl::Variant &def, const std::string &unit) { db::PCellParameterDeclaration *pd = new db::PCellParameterDeclaration (); pd->set_name (name); @@ -803,20 +785,7 @@ db::PCellParameterDeclaration *ctor_pcell_parameter_3 (const std::string &name, } Class decl_PCellParameterDeclaration ("db", "PCellParameterDeclaration", - gsi::constructor ("new", &ctor_pcell_parameter, gsi::arg ("name"), gsi::arg ("type"), gsi::arg ("description"), - "@brief Create a new parameter declaration with the given name and type\n" - "@param name The parameter name\n" - "@param type One of the Type... constants describing the type of the parameter\n" - "@param description The description text\n" - ) + - gsi::constructor ("new", &ctor_pcell_parameter_2, gsi::arg ("name"), gsi::arg ("type"), gsi::arg ("description"), gsi::arg ("default"), - "@brief Create a new parameter declaration with the given name, type and default value\n" - "@param name The parameter name\n" - "@param type One of the Type... constants describing the type of the parameter\n" - "@param description The description text\n" - "@param default The default (initial) value\n" - ) + - gsi::constructor ("new", &ctor_pcell_parameter_3, gsi::arg ("name"), gsi::arg ("type"), gsi::arg ("description"), gsi::arg ("default"), gsi::arg ("unit"), + gsi::constructor ("new", &ctor_pcell_parameter, gsi::arg ("name"), gsi::arg ("type"), gsi::arg ("description"), gsi::arg ("default", tl::Variant (), "nil"), gsi::arg ("unit", std::string ()), "@brief Create a new parameter declaration with the given name, type, default value and unit string\n" "@param name The parameter name\n" "@param type One of the Type... constants describing the type of the parameter\n" @@ -874,6 +843,7 @@ Class decl_PCellParameterDeclaration ("db", "PCel "This method will add the given value with the given description to the list of\n" "choices. If choices are defined, KLayout will show a drop-down box instead of an\n" "entry field in the parameter user interface.\n" + "If a range is already set for this parameter the choice will not be added and a warning message is showed.\n" ) + gsi::method ("choice_values", &db::PCellParameterDeclaration::get_choices, "@brief Returns a list of choice values\n" @@ -881,6 +851,44 @@ Class decl_PCellParameterDeclaration ("db", "PCel gsi::method ("choice_descriptions", &db::PCellParameterDeclaration::get_choice_descriptions, "@brief Returns a list of choice descriptions\n" ) + + gsi::method ("min_value", &db::PCellParameterDeclaration::min_value, + "@brief Gets the minimum value allowed\n" + "See \\min_value= for a description of this attribute.\n" + "\n" + "This attribute has been added in version 0.29." + ) + + gsi::method ("min_value=", &db::PCellParameterDeclaration::set_min_value, gsi::arg ("value"), + "@brief Sets the minimum value allowed\n" + "The minimum value is a visual feature and limits the allowed values for numerical\n" + "entry boxes. This applies to parameters of type int or double. The minimum value\n" + "is not effective if choices are present.\n" + "\n" + "The minimum value is not enforced - for example there is no restriction implemented\n" + "when setting values programmatically.\n" + "\n" + "Setting this attribute to \"nil\" (the default) implies \"no limit\".\n" + "\n" + "This attribute has been added in version 0.29." + ) + + gsi::method ("max_value", &db::PCellParameterDeclaration::max_value, + "@brief Gets the maximum value allowed\n" + "See \\max_value= for a description of this attribute.\n" + "\n" + "This attribute has been added in version 0.29." + ) + + gsi::method ("max_value=", &db::PCellParameterDeclaration::set_max_value, gsi::arg ("value"), + "@brief Sets the maximum value allowed\n" + "The maximum value is a visual feature and limits the allowed values for numerical\n" + "entry boxes. This applies to parameters of type int or double. The maximum value\n" + "is not effective if choices are present.\n" + "\n" + "The maximum value is not enforced - for example there is no restriction implemented\n" + "when setting values programmatically.\n" + "\n" + "Setting this attribute to \"nil\" (the default) implies \"no limit\".\n" + "\n" + "This attribute has been added in version 0.29." + ) + gsi::method ("default", &db::PCellParameterDeclaration::get_default, "@brief Gets the default value\n" ) + diff --git a/src/edt/edt/edtPCellParametersPage.cc b/src/edt/edt/edtPCellParametersPage.cc index 2e5488d2e..bcd9ae8a2 100644 --- a/src/edt/edt/edtPCellParametersPage.cc +++ b/src/edt/edt/edtPCellParametersPage.cc @@ -399,6 +399,16 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P m_icon_widgets.push_back (icon_label); m_all_widgets.back ().push_back (icon_label); + std::string range; + + if (! p->min_value ().is_nil () || ! p->max_value ().is_nil ()) { + range = tl::sprintf ( + " [%s, %s]" , + p->min_value ().is_nil () ? "-\u221e" /*infinity*/ : p->min_value ().to_string (), + p->max_value ().is_nil () ? "\u221e" /*infinity*/ : p->max_value ().to_string () + ); + } + if (p->get_type () != db::PCellParameterDeclaration::t_callback) { std::string leader; @@ -406,7 +416,8 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P leader = tl::sprintf ("[%s] ", p->get_name ()); } - QLabel *l = new QLabel (tl::to_qstring (leader + description), inner_frame); + QLabel *l = new QLabel (tl::to_qstring (leader + description + range), inner_frame); + inner_grid->addWidget (l, row, 1); m_all_widgets.back ().push_back (l); @@ -702,9 +713,11 @@ PCellParametersPage::do_parameter_changed () bool ok = true; db::ParameterStates states = m_states; get_parameters (states, &ok); // includes coerce - update_widgets_from_states (states); - if (ok && ! lazy_evaluation ()) { - emit edited (); + if (ok) { + update_widgets_from_states (states); + if (! lazy_evaluation ()) { + emit edited (); + } } } @@ -762,6 +775,8 @@ PCellParametersPage::get_parameters_internal (db::ParameterStates &states, bool ps.set_value (tl::Variant (v)); lay::indicate_error (le, (tl::Exception *) 0); + check_range(tl::Variant (v), *p); + } catch (tl::Exception &ex) { lay::indicate_error (le, &ex); @@ -786,6 +801,8 @@ PCellParametersPage::get_parameters_internal (db::ParameterStates &states, bool ps.set_value (tl::Variant (v)); lay::indicate_error (le, (tl::Exception *) 0); + check_range(tl::Variant (v), *p); + } catch (tl::Exception &ex) { lay::indicate_error (le, &ex); @@ -1085,6 +1102,18 @@ PCellParametersPage::states_from_parameters (db::ParameterStates &states, const } } +void +PCellParametersPage::check_range (const tl::Variant &value, const db::PCellParameterDeclaration &decl) +{ + if (! decl.min_value ().is_nil () && value < decl.min_value ()) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("The value is lower than the minimum allowed value: given value is %s, minimum value is %s")), value.to_string (), decl.min_value ().to_string ())); + } + + if (! decl.max_value ().is_nil () && ! (value < decl.max_value () || value == decl.max_value ())) { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("The value is higher than the maximum allowed value: given value is %s, maximum value is %s")), value.to_string (), decl.max_value ().to_string ())); + } +} + } #endif diff --git a/src/edt/edt/edtPCellParametersPage.h b/src/edt/edt/edtPCellParametersPage.h index e6e501399..a7b528371 100644 --- a/src/edt/edt/edtPCellParametersPage.h +++ b/src/edt/edt/edtPCellParametersPage.h @@ -181,6 +181,7 @@ private: void get_parameters_internal (db::ParameterStates &states, bool &edit_error); std::vector parameter_from_states (const db::ParameterStates &states) const; void states_from_parameters (db::ParameterStates &states, const std::vector ¶meters); + void check_range (const tl::Variant& value, const db::PCellParameterDeclaration &decl); }; } diff --git a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py index 5bdb73e4e..5beff2043 100644 --- a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py +++ b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py @@ -66,7 +66,7 @@ class _PCellDeclarationHelperMixin: self.layer = None self.cell = None - def param(self, name, value_type, description, hidden = False, readonly = False, unit = None, default = None, choices = None): + def param(self, name, value_type, description, hidden = False, readonly = False, unit = None, default = None, choices = None, min_value = None, max_value = None): """ Defines a parameter name -> the short name of the parameter @@ -76,6 +76,8 @@ class _PCellDeclarationHelperMixin: hidden -> (boolean) true, if the parameter is not shown in the dialog readonly -> (boolean) true, if the parameter cannot be edited unit -> the unit string + min_value -> the minimum value (only effective for numerical types and if no choices are present) + max_value -> the maximum value (only effective for numerical types and if no choices are present) default -> the default value choices -> ([ [ d, v ], ...) choice descriptions/value for choice type this method defines accessor methods for the parameters @@ -102,6 +104,8 @@ class _PCellDeclarationHelperMixin: pdecl.readonly = readonly if not (default is None): pdecl.default = default + pdecl.min_value = min_value + pdecl.max_value = max_value if not (unit is None): pdecl.unit = unit if not (choices is None): diff --git a/src/pymod/distutils_src/klayout/dbcore.pyi b/src/pymod/distutils_src/klayout/dbcore.pyi index 8cd688a4f..b6244d346 100644 --- a/src/pymod/distutils_src/klayout/dbcore.pyi +++ b/src/pymod/distutils_src/klayout/dbcore.pyi @@ -3941,12 +3941,16 @@ class CompoundRegionOperationNode: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -3958,6 +3962,10 @@ class CompoundRegionOperationNode: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: CompoundRegionOperationNode.GeometricalOp) -> bool: r""" @@ -3986,6 +3994,10 @@ class CompoundRegionOperationNode: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -4034,6 +4046,10 @@ class CompoundRegionOperationNode: r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -4044,6 +4060,10 @@ class CompoundRegionOperationNode: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: CompoundRegionOperationNode.LogicalOp) -> bool: r""" @@ -4072,6 +4092,10 @@ class CompoundRegionOperationNode: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -4125,12 +4149,16 @@ class CompoundRegionOperationNode: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -4142,6 +4170,10 @@ class CompoundRegionOperationNode: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: CompoundRegionOperationNode.ParameterType) -> bool: r""" @@ -4170,6 +4202,10 @@ class CompoundRegionOperationNode: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -4215,12 +4251,16 @@ class CompoundRegionOperationNode: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -4232,6 +4272,10 @@ class CompoundRegionOperationNode: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: CompoundRegionOperationNode.RatioParameterType) -> bool: r""" @@ -4260,6 +4304,10 @@ class CompoundRegionOperationNode: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -4305,12 +4353,16 @@ class CompoundRegionOperationNode: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -4322,6 +4374,10 @@ class CompoundRegionOperationNode: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: CompoundRegionOperationNode.ResultType) -> bool: r""" @@ -4335,12 +4391,12 @@ class CompoundRegionOperationNode: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ def __repr__(self) -> str: r""" @@ -4350,6 +4406,10 @@ class CompoundRegionOperationNode: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -5069,11 +5129,12 @@ class CplxTrans: "mirroring" describes a reflection at the x-axis which is included in the transformation prior to rotation.@param m The new mirror flag """ @classmethod - def from_dtrans(cls, trans: DCplxTrans) -> CplxTrans: + def from_dtrans(cls, trans: DCplxTrans, dbu: Optional[float] = ...) -> CplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_dtrans'. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @classmethod def from_s(cls, s: str) -> CplxTrans: @@ -5091,7 +5152,7 @@ class CplxTrans: """ @overload @classmethod - def new(cls, c: CplxTrans, m: Optional[float] = ..., u: Optional[DVector] = ...) -> CplxTrans: + def new(cls, c: CplxTrans, mag: Optional[float] = ..., u: Optional[DVector] = ...) -> CplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -5104,7 +5165,7 @@ class CplxTrans: """ @overload @classmethod - def new(cls, c: CplxTrans, m: float, x: int, y: int) -> CplxTrans: + def new(cls, c: CplxTrans, mag: Optional[float] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> CplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -5118,15 +5179,7 @@ class CplxTrans: """ @overload @classmethod - def new(cls, m: float) -> CplxTrans: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, u: DVector) -> CplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[DVector] = ...) -> CplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -5140,7 +5193,7 @@ class CplxTrans: """ @overload @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, x: float, y: float) -> CplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> CplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -5155,15 +5208,7 @@ class CplxTrans: """ @overload @classmethod - def new(cls, t: Trans) -> CplxTrans: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - @classmethod - def new(cls, t: Trans, m: float) -> CplxTrans: + def new(cls, t: Trans, mag: Optional[float] = ...) -> CplxTrans: r""" @brief Creates a transformation from a simple transformation and a magnification @@ -5171,27 +5216,30 @@ class CplxTrans: """ @overload @classmethod - def new(cls, trans: DCplxTrans) -> CplxTrans: + def new(cls, trans: DCplxTrans, dbu: Optional[float] = ...) -> CplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_dtrans'. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: ICplxTrans) -> CplxTrans: + def new(cls, trans: ICplxTrans, dbu: Optional[float] = ...) -> CplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the output space from integer units to floating-point units. Formally, the CplxTrans transformation is initialized with 'from_dbu * trans' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: VCplxTrans) -> CplxTrans: + def new(cls, trans: VCplxTrans, dbu: Optional[float] = ...) -> CplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input and output space from integer units to floating-point units and vice versa. Formally, the DCplxTrans transformation is initialized with 'from_dbu * trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod @@ -5240,7 +5288,7 @@ class CplxTrans: @brief Creates a unit transformation """ @overload - def __init__(self, c: CplxTrans, m: Optional[float] = ..., u: Optional[DVector] = ...) -> None: + def __init__(self, c: CplxTrans, mag: Optional[float] = ..., u: Optional[DVector] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -5252,7 +5300,7 @@ class CplxTrans: @param u The Additional displacement """ @overload - def __init__(self, c: CplxTrans, m: float, x: int, y: int) -> None: + def __init__(self, c: CplxTrans, mag: Optional[float] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -5265,14 +5313,7 @@ class CplxTrans: @param y The Additional displacement (y) """ @overload - def __init__(self, m: float) -> None: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - def __init__(self, mag: float, rot: float, mirrx: bool, u: DVector) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[DVector] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -5285,7 +5326,7 @@ class CplxTrans: @param u The displacement """ @overload - def __init__(self, mag: float, rot: float, mirrx: bool, x: float, y: float) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -5299,39 +5340,35 @@ class CplxTrans: @param y The y displacement """ @overload - def __init__(self, t: Trans) -> None: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - def __init__(self, t: Trans, m: float) -> None: + def __init__(self, t: Trans, mag: Optional[float] = ...) -> None: r""" @brief Creates a transformation from a simple transformation and a magnification Creates a magnifying transformation from a simple transformation and a magnification. """ @overload - def __init__(self, trans: DCplxTrans) -> None: + def __init__(self, trans: DCplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_dtrans'. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: ICplxTrans) -> None: + def __init__(self, trans: ICplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the output space from integer units to floating-point units. Formally, the CplxTrans transformation is initialized with 'from_dbu * trans' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: VCplxTrans) -> None: + def __init__(self, trans: VCplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input and output space from integer units to floating-point units and vice versa. Formally, the DCplxTrans transformation is initialized with 'from_dbu * trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload def __init__(self, u: DVector) -> None: @@ -5769,9 +5806,13 @@ class CplxTrans: r""" @brief Converts the transformation to another transformation with integer input and output coordinates - The database unit can be specified to translate the floating-point coordinate displacement in micron units to an integer-coordinate displacement in database units. The displacement's' coordinates will be divided by the database unit. + This method is redundant with the conversion constructors. Instead of 'to_itrans' use the conversion constructor: - This method has been introduced in version 0.25. + @code + itrans = RBA::ICplxTrans::new(trans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ def to_s(self, lazy: Optional[bool] = ..., dbu: Optional[float] = ...) -> str: r""" @@ -5781,19 +5822,29 @@ class CplxTrans: The lazy and DBU arguments have been added in version 0.27.6. """ - def to_trans(self) -> DCplxTrans: + def to_trans(self, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Converts the transformation to another transformation with floating-point input coordinates - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors. Instead of 'to_trans' use the conversion constructor: + + @code + dtrans = RBA::DCplxTrans::new(trans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ def to_vtrans(self, dbu: Optional[float] = ...) -> VCplxTrans: r""" @brief Converts the transformation to another transformation with integer output and floating-point input coordinates - The database unit can be specified to translate the floating-point coordinate displacement in micron units to an integer-coordinate displacement in database units. The displacement's' coordinates will be divided by the database unit. + This method is redundant with the conversion constructors. Instead of 'to_vtrans' use the conversion constructor: - This method has been introduced in version 0.25. + @code + vtrans = RBA::VCplxTrans::new(trans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ @overload def trans(self, box: Box) -> DBox: @@ -7236,11 +7287,12 @@ class DCplxTrans: "mirroring" describes a reflection at the x-axis which is included in the transformation prior to rotation.@param m The new mirror flag """ @classmethod - def from_itrans(cls, trans: CplxTrans) -> DCplxTrans: + def from_itrans(cls, trans: CplxTrans, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input space from integer units to floating-point units. Formally, the DCplxTrans transformation is initialized with 'trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_itrans'. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @classmethod def from_s(cls, s: str) -> DCplxTrans: @@ -7258,7 +7310,7 @@ class DCplxTrans: """ @overload @classmethod - def new(cls, c: DCplxTrans, m: Optional[float] = ..., u: Optional[DVector] = ...) -> DCplxTrans: + def new(cls, c: DCplxTrans, mag: Optional[float] = ..., u: Optional[DVector] = ...) -> DCplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -7271,7 +7323,7 @@ class DCplxTrans: """ @overload @classmethod - def new(cls, c: DCplxTrans, m: float, x: float, y: float) -> DCplxTrans: + def new(cls, c: DCplxTrans, mag: Optional[float] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -7285,15 +7337,7 @@ class DCplxTrans: """ @overload @classmethod - def new(cls, m: float) -> DCplxTrans: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, u: DVector) -> DCplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[DVector] = ...) -> DCplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -7307,7 +7351,7 @@ class DCplxTrans: """ @overload @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, x: float, y: float) -> DCplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -7322,15 +7366,7 @@ class DCplxTrans: """ @overload @classmethod - def new(cls, t: DTrans) -> DCplxTrans: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - @classmethod - def new(cls, t: DTrans, m: float) -> DCplxTrans: + def new(cls, t: DTrans, mag: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a transformation from a simple transformation and a magnification @@ -7338,27 +7374,30 @@ class DCplxTrans: """ @overload @classmethod - def new(cls, trans: CplxTrans) -> DCplxTrans: + def new(cls, trans: CplxTrans, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input space from integer units to floating-point units. Formally, the DCplxTrans transformation is initialized with 'trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_itrans'. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: ICplxTrans) -> DCplxTrans: + def new(cls, trans: ICplxTrans, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input and output space from integer units to floating-point units and vice versa. Formally, the DCplxTrans transformation is initialized with 'from_dbu * trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: VCplxTrans) -> DCplxTrans: + def new(cls, trans: VCplxTrans, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the output space from integer units to floating-point units. Formally, the DCplxTrans transformation is initialized with 'from_dbu * trans' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod @@ -7407,7 +7446,7 @@ class DCplxTrans: @brief Creates a unit transformation """ @overload - def __init__(self, c: DCplxTrans, m: Optional[float] = ..., u: Optional[DVector] = ...) -> None: + def __init__(self, c: DCplxTrans, mag: Optional[float] = ..., u: Optional[DVector] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -7419,7 +7458,7 @@ class DCplxTrans: @param u The Additional displacement """ @overload - def __init__(self, c: DCplxTrans, m: float, x: float, y: float) -> None: + def __init__(self, c: DCplxTrans, mag: Optional[float] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -7432,14 +7471,7 @@ class DCplxTrans: @param y The Additional displacement (y) """ @overload - def __init__(self, m: float) -> None: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - def __init__(self, mag: float, rot: float, mirrx: bool, u: DVector) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[DVector] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -7452,7 +7484,7 @@ class DCplxTrans: @param u The displacement """ @overload - def __init__(self, mag: float, rot: float, mirrx: bool, x: float, y: float) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -7466,39 +7498,35 @@ class DCplxTrans: @param y The y displacement """ @overload - def __init__(self, t: DTrans) -> None: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - def __init__(self, t: DTrans, m: float) -> None: + def __init__(self, t: DTrans, mag: Optional[float] = ...) -> None: r""" @brief Creates a transformation from a simple transformation and a magnification Creates a magnifying transformation from a simple transformation and a magnification. """ @overload - def __init__(self, trans: CplxTrans) -> None: + def __init__(self, trans: CplxTrans, dbu: Optional[float] = ...) -> None: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input space from integer units to floating-point units. Formally, the DCplxTrans transformation is initialized with 'trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_itrans'. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: ICplxTrans) -> None: + def __init__(self, trans: ICplxTrans, dbu: Optional[float] = ...) -> None: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the input and output space from integer units to floating-point units and vice versa. Formally, the DCplxTrans transformation is initialized with 'from_dbu * trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: VCplxTrans) -> None: + def __init__(self, trans: VCplxTrans, dbu: Optional[float] = ...) -> None: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour + The 'dbu' argument is used to transform the output space from integer units to floating-point units. Formally, the DCplxTrans transformation is initialized with 'from_dbu * trans' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. - This constructor has been introduced in version 0.25. + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload def __init__(self, u: DVector) -> None: @@ -7928,7 +7956,13 @@ class DCplxTrans: The database unit can be specified to translate the floating-point coordinate displacement in micron units to an integer-coordinate displacement in database units. The displacement's' coordinates will be divided by the database unit. - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors. Instead of 'to_itrans' use the conversion constructor: + + @code + itrans = RBA::ICplxTrans::new(dtrans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ def to_s(self, lazy: Optional[bool] = ..., dbu: Optional[float] = ...) -> str: r""" @@ -7938,11 +7972,17 @@ class DCplxTrans: The lazy and DBU arguments have been added in version 0.27.6. """ - def to_trans(self) -> CplxTrans: + def to_trans(self, dbu: Optional[float] = ...) -> CplxTrans: r""" @brief Converts the transformation to another transformation with integer input coordinates - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors. Instead of 'to_trans' use the conversion constructor: + + @code + trans = RBA::CplxTrans::new(dtrans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ def to_vtrans(self, dbu: Optional[float] = ...) -> VCplxTrans: r""" @@ -7950,7 +7990,13 @@ class DCplxTrans: The database unit can be specified to translate the floating-point coordinate displacement in micron units to an integer-coordinate displacement in database units. The displacement's' coordinates will be divided by the database unit. - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors. Instead of 'to_vtrans' use the conversion constructor: + + @code + vtrans = RBA::VCplxTrans::new(dtrans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ @overload def trans(self, box: DBox) -> DBox: @@ -11339,8 +11385,7 @@ class DText: Setter: @brief Sets the horizontal alignment - This property specifies how the text is aligned relative to the anchor point. - This property has been introduced in version 0.22 and extended to enums in 0.28. + This is the version accepting integer values. It's provided for backward compatibility. """ size: float r""" @@ -11886,7 +11931,7 @@ class DTrans: """ @overload @classmethod - def new(cls, c: DTrans, x: float, y: float) -> DTrans: + def new(cls, c: DTrans, x: Optional[float] = ..., y: Optional[float] = ...) -> DTrans: r""" @brief Creates a transformation from another transformation plus a displacement @@ -11900,7 +11945,7 @@ class DTrans: """ @overload @classmethod - def new(cls, rot: int, mirr: Optional[bool] = ..., u: Optional[DVector] = ...) -> DTrans: + def new(cls, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., u: Optional[DVector] = ...) -> DTrans: r""" @brief Creates a transformation using angle and mirror flag @@ -11913,7 +11958,7 @@ class DTrans: """ @overload @classmethod - def new(cls, rot: int, mirr: bool, x: float, y: float) -> DTrans: + def new(cls, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> DTrans: r""" @brief Creates a transformation using angle and mirror flag and two coordinate values for displacement @@ -11987,7 +12032,7 @@ class DTrans: @param u The Additional displacement """ @overload - def __init__(self, c: DTrans, x: float, y: float) -> None: + def __init__(self, c: DTrans, x: Optional[float] = ..., y: Optional[float] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a displacement @@ -12000,7 +12045,7 @@ class DTrans: @param y The Additional displacement (y) """ @overload - def __init__(self, rot: int, mirr: Optional[bool] = ..., u: Optional[DVector] = ...) -> None: + def __init__(self, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., u: Optional[DVector] = ...) -> None: r""" @brief Creates a transformation using angle and mirror flag @@ -12012,7 +12057,7 @@ class DTrans: @param u The displacement """ @overload - def __init__(self, rot: int, mirr: bool, x: float, y: float) -> None: + def __init__(self, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., x: Optional[float] = ..., y: Optional[float] = ...) -> None: r""" @brief Creates a transformation using angle and mirror flag and two coordinate values for displacement @@ -19038,6 +19083,10 @@ class Edges(ShapeCollection): r""" @brief Compares an enum with an integer value """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -19048,6 +19097,10 @@ class Edges(ShapeCollection): r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: Edges.EdgeType) -> bool: r""" @@ -19076,6 +19129,10 @@ class Edges(ShapeCollection): r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -21852,12 +21909,16 @@ class HAlign: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -21869,6 +21930,10 @@ class HAlign: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: HAlign) -> bool: r""" @@ -21882,12 +21947,12 @@ class HAlign: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ def __repr__(self) -> str: r""" @@ -21959,6 +22024,10 @@ class HAlign: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -22090,11 +22159,13 @@ class ICplxTrans: "mirroring" describes a reflection at the x-axis which is included in the transformation prior to rotation.@param m The new mirror flag """ @classmethod - def from_dtrans(cls, trans: DCplxTrans) -> ICplxTrans: + def from_dtrans(cls, trans: DCplxTrans, dbu: Optional[float] = ...) -> ICplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_dtrans'. + The 'dbu' argument is used to transform the input space and output space from floating-point units to integer units and vice versa. Formally, the ICplxTrans transformation is initialized with 'to_dbu * trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)' and 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @classmethod def from_s(cls, s: str) -> ICplxTrans: @@ -22105,11 +22176,13 @@ class ICplxTrans: This method has been added in version 0.23. """ @classmethod - def from_trans(cls, trans: CplxTrans) -> ICplxTrans: + def from_trans(cls, trans: CplxTrans, dbu: Optional[float] = ...) -> ICplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_trans'. + The 'dbu' argument is used to transform the output space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'to_dbu * trans' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod @@ -22119,7 +22192,7 @@ class ICplxTrans: """ @overload @classmethod - def new(cls, c: ICplxTrans, m: Optional[float] = ..., u: Optional[Vector] = ...) -> ICplxTrans: + def new(cls, c: ICplxTrans, mag: Optional[float] = ..., u: Optional[Vector] = ...) -> ICplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -22132,7 +22205,7 @@ class ICplxTrans: """ @overload @classmethod - def new(cls, c: ICplxTrans, m: float, x: int, y: int) -> ICplxTrans: + def new(cls, c: ICplxTrans, mag: Optional[float] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> ICplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -22146,15 +22219,7 @@ class ICplxTrans: """ @overload @classmethod - def new(cls, m: float) -> ICplxTrans: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, u: Vector) -> ICplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[Vector] = ...) -> ICplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -22168,7 +22233,7 @@ class ICplxTrans: """ @overload @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, x: int, y: int) -> ICplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> ICplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -22183,15 +22248,7 @@ class ICplxTrans: """ @overload @classmethod - def new(cls, t: Trans) -> ICplxTrans: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - @classmethod - def new(cls, t: Trans, m: float) -> ICplxTrans: + def new(cls, t: Trans, mag: Optional[float] = ...) -> ICplxTrans: r""" @brief Creates a transformation from a simple transformation and a magnification @@ -22199,27 +22256,33 @@ class ICplxTrans: """ @overload @classmethod - def new(cls, trans: CplxTrans) -> ICplxTrans: + def new(cls, trans: CplxTrans, dbu: Optional[float] = ...) -> ICplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_trans'. + The 'dbu' argument is used to transform the output space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'to_dbu * trans' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: DCplxTrans) -> ICplxTrans: + def new(cls, trans: DCplxTrans, dbu: Optional[float] = ...) -> ICplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_dtrans'. + The 'dbu' argument is used to transform the input space and output space from floating-point units to integer units and vice versa. Formally, the ICplxTrans transformation is initialized with 'to_dbu * trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)' and 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: VCplxTrans) -> ICplxTrans: + def new(cls, trans: VCplxTrans, dbu: Optional[float] = ...) -> ICplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25. + The 'dbu' argument is used to transform the input space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload @classmethod @@ -22268,7 +22331,7 @@ class ICplxTrans: @brief Creates a unit transformation """ @overload - def __init__(self, c: ICplxTrans, m: Optional[float] = ..., u: Optional[Vector] = ...) -> None: + def __init__(self, c: ICplxTrans, mag: Optional[float] = ..., u: Optional[Vector] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -22280,7 +22343,7 @@ class ICplxTrans: @param u The Additional displacement """ @overload - def __init__(self, c: ICplxTrans, m: float, x: int, y: int) -> None: + def __init__(self, c: ICplxTrans, mag: Optional[float] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -22293,14 +22356,7 @@ class ICplxTrans: @param y The Additional displacement (y) """ @overload - def __init__(self, m: float) -> None: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - def __init__(self, mag: float, rot: float, mirrx: bool, u: Vector) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[Vector] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -22313,7 +22369,7 @@ class ICplxTrans: @param u The displacement """ @overload - def __init__(self, mag: float, rot: float, mirrx: bool, x: int, y: int) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -22327,39 +22383,38 @@ class ICplxTrans: @param y The y displacement """ @overload - def __init__(self, t: Trans) -> None: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - def __init__(self, t: Trans, m: float) -> None: + def __init__(self, t: Trans, mag: Optional[float] = ...) -> None: r""" @brief Creates a transformation from a simple transformation and a magnification Creates a magnifying transformation from a simple transformation and a magnification. """ @overload - def __init__(self, trans: CplxTrans) -> None: + def __init__(self, trans: CplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_trans'. + The 'dbu' argument is used to transform the output space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'to_dbu * trans' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: DCplxTrans) -> None: + def __init__(self, trans: DCplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25 and replaces the previous static method 'from_dtrans'. + The 'dbu' argument is used to transform the input space and output space from floating-point units to integer units and vice versa. Formally, the ICplxTrans transformation is initialized with 'to_dbu * trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)' and 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: VCplxTrans) -> None: + def __init__(self, trans: VCplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates an integer coordinate transformation from another coordinate flavour - This constructor has been introduced in version 0.25. + The 'dbu' argument is used to transform the input space from floating-point units to integer units. Formally, the CplxTrans transformation is initialized with 'trans * from_dbu' where 'from_dbu' is the transformation into micrometer space, or more precisely 'CplxTrans(mag=dbu)'. + + This constructor has been introduced in version 0.25. The 'dbu' argument has been added in version 0.29. """ @overload def __init__(self, u: Vector) -> None: @@ -22789,7 +22844,13 @@ class ICplxTrans: The database unit can be specified to translate the integer coordinate displacement in database units to a floating-point displacement in micron units. The displacement's' coordinates will be multiplied with the database unit. - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors and is ill-named. Instead of 'to_itrans' use the conversion constructor: + + @code + dtrans = RBA::DCplxTrans::new(itrans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ def to_s(self, lazy: Optional[bool] = ..., dbu: Optional[float] = ...) -> str: r""" @@ -22799,11 +22860,17 @@ class ICplxTrans: The lazy and DBU arguments have been added in version 0.27.6. """ - def to_trans(self) -> VCplxTrans: + def to_trans(self, dbu: Optional[float] = ...) -> VCplxTrans: r""" @brief Converts the transformation to another transformation with floating-point input coordinates - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors and is ill-named. Instead of 'to_trans' use the conversion constructor: + + @code + vtrans = RBA::VCplxTrans::new(itrans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ def to_vtrans(self, dbu: Optional[float] = ...) -> CplxTrans: r""" @@ -22811,7 +22878,13 @@ class ICplxTrans: The database unit can be specified to translate the integer coordinate displacement in database units to a floating-point displacement in micron units. The displacement's' coordinates will be multiplied with the database unit. - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors and is ill-named. Instead of 'to_vtrans' use the conversion constructor: + + @code + trans = RBA::CplxTrans::new(itrans, dbu) + @/code + + This method has been introduced in version 0.25 and was deprecated in version 0.29. """ @overload def trans(self, box: Box) -> Box: @@ -23917,11 +23990,11 @@ class Instance: Starting with version 0.25 the displacement is of vector type. Setter: - @brief Sets the displacement vector for the 'b' axis + @brief Sets the displacement vector for the 'b' axis in micrometer units - If the instance was not an array instance before it is made one. + Like \b= with an integer displacement, this method will set the displacement vector but it accepts a vector in micrometer units that is of \DVector type. The vector will be translated to database units internally. - This method has been introduced in version 0.23. Starting with version 0.25 the displacement is of vector type. + This method has been introduced in version 0.25. """ cell: Cell r""" @@ -24096,10 +24169,9 @@ class Instance: @brief Gets the transformation of the instance or the first instance in the array The transformation returned is only valid if the array does not represent a complex transformation array Setter: - @brief Sets the transformation of the instance or the first instance in the array (in micrometer units) - This method sets the transformation the same way as \cplx_trans=, but the displacement of this transformation is given in micrometer units. It is internally translated into database units. + @brief Sets the transformation of the instance or the first instance in the array - This method has been introduced in version 0.25. + This method has been introduced in version 0.23. """ @classmethod def new(cls) -> Instance: @@ -25906,7 +25978,7 @@ class LayerMap: The LayerMap class has been introduced in version 0.18. Target layer have been introduced in version 0.20. 1:n mapping and unmapping has been introduced in version 0.27. """ @classmethod - def from_string(cls, arg0: str) -> LayerMap: + def from_string(cls, s: str) -> LayerMap: r""" @brief Creates a layer map from the given string The format of the string is that used in layer mapping files: one mapping entry per line, comments are allowed using '#' or '//'. The format of each line is that used in the 'map(string, index)' method. @@ -29212,6 +29284,10 @@ class LayoutToNetlist: r""" @brief Compares an enum with an integer value """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -29222,6 +29298,10 @@ class LayoutToNetlist: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: LayoutToNetlist.BuildNetHierarchyMode) -> bool: r""" @@ -29235,12 +29315,12 @@ class LayoutToNetlist: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ def __repr__(self) -> str: r""" @@ -29250,6 +29330,10 @@ class LayoutToNetlist: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -30524,12 +30608,16 @@ class LoadLayoutOptions: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -30541,6 +30629,10 @@ class LoadLayoutOptions: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: LoadLayoutOptions.CellConflictResolution) -> bool: r""" @@ -30569,6 +30661,10 @@ class LoadLayoutOptions: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -32740,6 +32836,10 @@ class Metrics: r""" @brief Compares an enum with an integer value """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -32750,6 +32850,10 @@ class Metrics: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: Metrics) -> bool: r""" @@ -32840,6 +32944,10 @@ class Metrics: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -33260,14 +33368,14 @@ class NetPinRef: @overload def net(self) -> Net: r""" - @brief Gets the net this pin reference is attached to (non-const version). - - This constness variant has been introduced in version 0.26.8 + @brief Gets the net this pin reference is attached to. """ @overload def net(self) -> Net: r""" - @brief Gets the net this pin reference is attached to. + @brief Gets the net this pin reference is attached to (non-const version). + + This constness variant has been introduced in version 0.26.8 """ def pin(self) -> Pin: r""" @@ -33488,12 +33596,6 @@ class NetTerminalRef: The latter may happen, if the object is owned by a C++ object which got destroyed itself. """ @overload - def device(self) -> Device: - r""" - @brief Gets the device reference. - Gets the device object that this connection is made to. - """ - @overload def device(self) -> Device: r""" @brief Gets the device reference (non-const version). @@ -33501,6 +33603,12 @@ class NetTerminalRef: This constness variant has been introduced in version 0.26.8 """ + @overload + def device(self) -> Device: + r""" + @brief Gets the device reference. + Gets the device object that this connection is made to. + """ def device_class(self) -> DeviceClass: r""" @brief Gets the class of the device which is addressed. @@ -33518,14 +33626,14 @@ class NetTerminalRef: @overload def net(self) -> Net: r""" - @brief Gets the net this terminal reference is attached to. + @brief Gets the net this terminal reference is attached to (non-const version). + + This constness variant has been introduced in version 0.26.8 """ @overload def net(self) -> Net: r""" - @brief Gets the net this terminal reference is attached to (non-const version). - - This constness variant has been introduced in version 0.26.8 + @brief Gets the net this terminal reference is attached to. """ def terminal_def(self) -> DeviceTerminalDefinition: r""" @@ -34314,6 +34422,14 @@ class Netlist: This constness variant has been introduced in version 0.26.8. """ @overload + def circuits_by_name(self, name_pattern: str) -> List[Circuit]: + r""" + @brief Gets the circuit objects for a given name filter. + The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern. + + This method has been introduced in version 0.26.4. + """ + @overload def circuits_by_name(self, name_pattern: str) -> List[Circuit]: r""" @brief Gets the circuit objects for a given name filter (const version). @@ -34322,14 +34438,6 @@ class Netlist: This constness variant has been introduced in version 0.26.8. """ - @overload - def circuits_by_name(self, name_pattern: str) -> List[Circuit]: - r""" - @brief Gets the circuit objects for a given name filter. - The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern. - - This method has been introduced in version 0.26.4. - """ def combine_devices(self) -> None: r""" @brief Combines devices where possible @@ -34384,24 +34492,16 @@ class Netlist: This constness variant has been introduced in version 0.26.8. """ @overload - def each_circuit_bottom_up(self) -> Iterator[Circuit]: - r""" - @brief Iterates over the circuits bottom-up (const version) - Iterating bottom-up means the parent circuits come after the child circuits. This is the basically the reverse order as delivered by \each_circuit_top_down. - - This constness variant has been introduced in version 0.26.8. - """ - @overload def each_circuit_bottom_up(self) -> Iterator[Circuit]: r""" @brief Iterates over the circuits bottom-up Iterating bottom-up means the parent circuits come after the child circuits. This is the basically the reverse order as delivered by \each_circuit_top_down. """ @overload - def each_circuit_top_down(self) -> Iterator[Circuit]: + def each_circuit_bottom_up(self) -> Iterator[Circuit]: r""" - @brief Iterates over the circuits top-down (const version) - Iterating top-down means the parent circuits come before the child circuits. The first \top_circuit_count circuits are top circuits - i.e. those which are not referenced by other circuits. + @brief Iterates over the circuits bottom-up (const version) + Iterating bottom-up means the parent circuits come after the child circuits. This is the basically the reverse order as delivered by \each_circuit_top_down. This constness variant has been introduced in version 0.26.8. """ @@ -34412,6 +34512,14 @@ class Netlist: Iterating top-down means the parent circuits come before the child circuits. The first \top_circuit_count circuits are top circuits - i.e. those which are not referenced by other circuits. """ @overload + def each_circuit_top_down(self) -> Iterator[Circuit]: + r""" + @brief Iterates over the circuits top-down (const version) + Iterating top-down means the parent circuits come before the child circuits. The first \top_circuit_count circuits are top circuits - i.e. those which are not referenced by other circuits. + + This constness variant has been introduced in version 0.26.8. + """ + @overload def each_device_class(self) -> Iterator[DeviceClass]: r""" @brief Iterates over the device classes of the netlist @@ -34440,7 +34548,7 @@ class Netlist: @brief Flattens circuits matching a certain pattern This method will substitute all instances (subcircuits) of all circuits with names matching the given name pattern. The name pattern is a glob expression. For example, 'flatten_circuit("np*")' will flatten all circuits with names starting with 'np'. """ - def flatten_circuits(self, arg0: Sequence[Circuit]) -> None: + def flatten_circuits(self, circuits: Sequence[Circuit]) -> None: r""" @brief Flattens all given circuits of the netlist This method is equivalent to calling \flatten_circuit for all given circuits, but more efficient. @@ -35084,6 +35192,10 @@ class NetlistCrossReference(NetlistCompareLogger): r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -35094,6 +35206,10 @@ class NetlistCrossReference(NetlistCompareLogger): r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: NetlistCrossReference.Status) -> bool: r""" @@ -35122,6 +35238,10 @@ class NetlistCrossReference(NetlistCompareLogger): r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -36013,7 +36133,7 @@ class NetlistSpiceWriter(NetlistWriter): """ @overload @classmethod - def new(cls, arg0: NetlistSpiceWriterDelegate) -> NetlistSpiceWriter: + def new(cls, delegate: NetlistSpiceWriterDelegate) -> NetlistSpiceWriter: r""" @brief Creates a new writer with a delegate. """ @@ -36031,7 +36151,7 @@ class NetlistSpiceWriter(NetlistWriter): @brief Creates a new writer without delegate. """ @overload - def __init__(self, arg0: NetlistSpiceWriterDelegate) -> None: + def __init__(self, delegate: NetlistSpiceWriterDelegate) -> None: r""" @brief Creates a new writer with a delegate. """ @@ -36597,6 +36717,46 @@ class PCellParameterDeclaration: Setter: @brief Makes the parameter hidden if this attribute is set to true """ + max_value: Any + r""" + Getter: + @brief Gets the maximum value allowed + See \max_value= for a description of this attribute. + + This attribute has been added in version 0.29. + Setter: + @brief Sets the maximum value allowed + The maximum value is a visual feature and limits the allowed values for numerical + entry boxes. This applies to parameters of type int or double. The maximum value + is not effective if choices are present. + + The maximum value is not enforced - for example there is no restriction implemented + when setting values programmatically. + + Setting this attribute to "nil" (the default) implies "no limit". + + This attribute has been added in version 0.29. + """ + min_value: Any + r""" + Getter: + @brief Gets the minimum value allowed + See \min_value= for a description of this attribute. + + This attribute has been added in version 0.29. + Setter: + @brief Sets the minimum value allowed + The minimum value is a visual feature and limits the allowed values for numerical + entry boxes. This applies to parameters of type int or double. The minimum value + is not effective if choices are present. + + The minimum value is not enforced - for example there is no restriction implemented + when setting values programmatically. + + Setting this attribute to "nil" (the default) implies "no limit". + + This attribute has been added in version 0.29. + """ name: str r""" Getter: @@ -36632,28 +36792,8 @@ class PCellParameterDeclaration: @brief Sets the unit string The unit string is shown right to the edit fields for numeric parameters. """ - @overload @classmethod - def new(cls, name: str, type: int, description: str) -> PCellParameterDeclaration: - r""" - @brief Create a new parameter declaration with the given name and type - @param name The parameter name - @param type One of the Type... constants describing the type of the parameter - @param description The description text - """ - @overload - @classmethod - def new(cls, name: str, type: int, description: str, default: Any) -> PCellParameterDeclaration: - r""" - @brief Create a new parameter declaration with the given name, type and default value - @param name The parameter name - @param type One of the Type... constants describing the type of the parameter - @param description The description text - @param default The default (initial) value - """ - @overload - @classmethod - def new(cls, name: str, type: int, description: str, default: Any, unit: str) -> PCellParameterDeclaration: + def new(cls, name: str, type: int, description: str, default: Optional[Any] = ..., unit: Optional[str] = ...) -> PCellParameterDeclaration: r""" @brief Create a new parameter declaration with the given name, type, default value and unit string @param name The parameter name @@ -36670,25 +36810,7 @@ class PCellParameterDeclaration: r""" @brief Creates a copy of self """ - @overload - def __init__(self, name: str, type: int, description: str) -> None: - r""" - @brief Create a new parameter declaration with the given name and type - @param name The parameter name - @param type One of the Type... constants describing the type of the parameter - @param description The description text - """ - @overload - def __init__(self, name: str, type: int, description: str, default: Any) -> None: - r""" - @brief Create a new parameter declaration with the given name, type and default value - @param name The parameter name - @param type One of the Type... constants describing the type of the parameter - @param description The description text - @param default The default (initial) value - """ - @overload - def __init__(self, name: str, type: int, description: str, default: Any, unit: str) -> None: + def __init__(self, name: str, type: int, description: str, default: Optional[Any] = ..., unit: Optional[str] = ...) -> None: r""" @brief Create a new parameter declaration with the given name, type, default value and unit string @param name The parameter name @@ -36740,6 +36862,7 @@ class PCellParameterDeclaration: This method will add the given value with the given description to the list of choices. If choices are defined, KLayout will show a drop-down box instead of an entry field in the parameter user interface. + If a range is already set for this parameter the choice will not be added and a warning message is showed. """ def assign(self, other: PCellParameterDeclaration) -> None: r""" @@ -36837,6 +36960,10 @@ class PCellParameterState: r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -36847,6 +36974,10 @@ class PCellParameterState: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: PCellParameterState.ParameterStateIcon) -> bool: r""" @@ -36860,12 +36991,12 @@ class PCellParameterState: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ def __repr__(self) -> str: r""" @@ -36875,6 +37006,10 @@ class PCellParameterState: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -39459,6 +39594,10 @@ class PreferredOrientation: r""" @brief Compares an enum with an integer value """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -39469,6 +39608,10 @@ class PreferredOrientation: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: PreferredOrientation) -> bool: r""" @@ -39559,6 +39702,10 @@ class PreferredOrientation: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -39637,12 +39784,16 @@ class PropertyConstraint: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -39654,6 +39805,10 @@ class PropertyConstraint: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: PropertyConstraint) -> bool: r""" @@ -39667,12 +39822,12 @@ class PropertyConstraint: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ def __repr__(self) -> str: r""" @@ -39744,6 +39899,10 @@ class PropertyConstraint: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -41112,6 +41271,10 @@ class Region(ShapeCollection): r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -41122,6 +41285,10 @@ class Region(ShapeCollection): r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: Region.OppositeFilter) -> bool: r""" @@ -41135,12 +41302,12 @@ class Region(ShapeCollection): @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ def __repr__(self) -> str: r""" @@ -41150,6 +41317,10 @@ class Region(ShapeCollection): r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -41218,6 +41389,10 @@ class Region(ShapeCollection): r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -41228,6 +41403,10 @@ class Region(ShapeCollection): r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: Region.RectFilter) -> bool: r""" @@ -41241,12 +41420,12 @@ class Region(ShapeCollection): @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ def __repr__(self) -> str: r""" @@ -41256,6 +41435,10 @@ class Region(ShapeCollection): r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -42142,6 +42325,32 @@ class Region(ShapeCollection): This method has been introduced in version 0.25. """ + @overload + def delaunay(self) -> Region: + r""" + @brief Computes a constrained Delaunay triangulation from the given region + + @return A new region holding the triangles of the constrained Delaunay triangulation. + + Note that the result is a region in raw mode as otherwise the triangles are likely to get merged later on. + + This method has been introduced in version 0.29. + """ + @overload + def delaunay(self, max_area: float, min_b: Optional[float] = ...) -> Region: + r""" + @brief Computes a refined, constrained Delaunay triangulation from the given region + + @return A new region holding the triangles of the refined, constrained Delaunay triangulation. + + Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles larger than this area will be split. In addition 'skinny' triangles will be resolved where possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). A minimum number for b can be given. The default of 1.0 corresponds to a minimum angle of 30 degree and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to a minimum angle of abouth 37 degree. + + The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. + + Note that the result is a region in raw mode as otherwise the triangles are likely to get merged later on. + + This method has been introduced in version 0.29. + """ def destroy(self) -> None: r""" @brief Explicitly destroys the object @@ -43246,6 +43455,40 @@ class Region(ShapeCollection): This method has been introduced in version 0.26.1 """ + @overload + def rasterize(self, origin: Point, pixel_distance: Vector, pixel_size: Vector, nx: int, ny: int) -> List[List[float]]: + r""" + @brief A version of 'rasterize' that allows a pixel step distance which is larger than the pixel size + This version behaves like the first variant of 'rasterize', but the pixel distance (pixel-to-pixel step raster) + can be specified separately from the pixel size. Currently, the pixel size must be equal or smaller than the + pixel distance - i.e. the pixels must not overlap. + + This method has been added in version 0.29. + """ + @overload + def rasterize(self, origin: Point, pixel_size: Vector, nx: int, ny: int) -> List[List[float]]: + r""" + @brief A grayscale rasterizer delivering the area covered per pixel + @param origin The lower-left corner of the lowest-left pixel + @param pixel_size The dimension of each pixel (the x component gives the width, the y component the height) + @param nx The number of pixels in horizontal direction + @param ny The number of pixels in vertical direction + The method will create a grayscale, high-resolution density map of a rectangular region. + The scan region is defined by the origin, the pixel size and the number of pixels in horizontal (nx) and + vertical (ny) direction. The resulting array will contain the area covered by polygons from the region + in square database units. + + For non-overlapping polygons, the maximum density value is px*py. Overlapping polygons are counted multiple + times, so the actual values may be larger. If you want overlaps removed, you have to + merge the region before. Merge semantics does not apply for the 'rasterize' method. + + The resulting area values are precise within the limits of double-precision floating point arithmetics. + + A second version exists that allows specifying an active pixel size which is smaller than the + pixel distance hence allowing pixels samples that do not cover the full area, but leave gaps between the pixels. + + This method has been added in version 0.29. + """ def rectangles(self) -> Region: r""" @brief Returns all polygons which are rectangles @@ -44891,6 +45134,10 @@ class Severity: r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -44901,6 +45148,10 @@ class Severity: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: Severity) -> bool: r""" @@ -44991,6 +45242,10 @@ class Severity: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -45265,11 +45520,12 @@ class Shape: This method has been introduced in version 0.23. Setter: - @brief Sets the upper right point of the box + @brief Sets the upper right corner of the box with the point being given in micrometer units Applies to boxes only. Changes the upper right point of the box and throws an exception if the shape is not a box. + Translation from micrometer units to database units is done internally. - This method has been introduced in version 0.23. + This method has been introduced in version 0.25. """ box_width: int r""" @@ -45588,11 +45844,10 @@ class Shape: Starting with version 0.23, this method returns nil, if the shape does not represent a geometrical primitive that can be converted to a polygon. Setter: - @brief Replaces the shape by the given polygon object - This method replaces the shape by the given polygon object. This method can only be called for editable layouts. It does not change the user properties of the shape. - Calling this method will invalidate any iterators. It should not be called inside a loop iterating over shapes. + @brief Replaces the shape by the given polygon (in micrometer units) + This method replaces the shape by the given polygon, like \polygon= with a \Polygon argument does. This version translates the polygon from micrometer units to database units internally. - This method has been introduced in version 0.22. + This method has been introduced in version 0.25. """ prop_id: int r""" @@ -48839,12 +49094,6 @@ class SubCircuit(NetlistObject): Usually it's not required to call this method. It has been introduced in version 0.24. """ @overload - def circuit(self) -> Circuit: - r""" - @brief Gets the circuit the subcircuit lives in. - This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref. - """ - @overload def circuit(self) -> Circuit: r""" @brief Gets the circuit the subcircuit lives in (non-const version). @@ -48853,6 +49102,12 @@ class SubCircuit(NetlistObject): This constness variant has been introduced in version 0.26.8 """ @overload + def circuit(self) -> Circuit: + r""" + @brief Gets the circuit the subcircuit lives in. + This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref. + """ + @overload def circuit_ref(self) -> Circuit: r""" @brief Gets the circuit referenced by the subcircuit (non-const version). @@ -48958,6 +49213,20 @@ class Technology: Setter: @hide """ + default_grids: List[float] + r""" + Getter: + @brief Gets the default grids + + See \default_grids for details. + + This property has been introduced in version 0.28.17. + Setter: + @brief Sets the default grids + If not empty, this list replaces the global grid list for this technology. + + This property has been introduced in version 0.28.17. + """ description: str r""" Getter: @@ -49410,8 +49679,7 @@ class Text: Setter: @brief Sets the horizontal alignment - This property specifies how the text is aligned relative to the anchor point. - This property has been introduced in version 0.22 and extended to enums in 0.28. + This is the version accepting integer values. It's provided for backward compatibility. """ size: int r""" @@ -49447,7 +49715,8 @@ class Text: Setter: @brief Sets the vertical alignment - This is the version accepting integer values. It's provided for backward compatibility. + This property specifies how the text is aligned relative to the anchor point. + This property has been introduced in version 0.22 and extended to enums in 0.28. """ x: int r""" @@ -49886,7 +50155,7 @@ class TextGenerator: @brief Creates a new object of this class """ @classmethod - def set_font_paths(cls, arg0: Sequence[str]) -> None: + def set_font_paths(cls, paths: Sequence[str]) -> None: r""" @brief Sets the paths where to look for font files This function sets the paths where to look for font files. After setting such a path, each font found will render a specific generator. The generator can be found under the font file's name. As the text generator is also the basis for the Basic.TEXT PCell, using this function also allows configuring custom fonts for this library cell. @@ -51267,6 +51536,14 @@ class TilingProcessor: @param edges The \Edges object to which the data is sent """ @overload + def output(self, name: str, image: lay.BasicImage) -> None: + r""" + @brief Specifies output to an image + This method will establish an output channel which delivers float data to image data. The image is a monochrome image where each pixel corresponds to a single tile. This method for example is useful to collect density information into an image. The image is configured such that each pixel covers one tile. + + The name is the name which must be used in the _output function of the scripts in order to address that channel. + """ + @overload def output(self, name: str, layout: Layout, cell: int, layer_index: int) -> None: r""" @brief Specifies output to a layout layer @@ -51293,6 +51570,14 @@ class TilingProcessor: @param lp The layer specification where the output will be sent to """ @overload + def output(self, name: str, rdb: rdb.ReportDatabase, cell_id: int, category_id: int) -> None: + r""" + @brief Specifies output to a report database + This method will establish an output channel for the processor. The output sent to that channel will be put into the report database given by the "rdb" parameter. "cell_id" specifies the cell and "category_id" the category to use. + + The name is the name which must be used in the _output function of the scripts in order to address that channel. + """ + @overload def output(self, name: str, rec: TileOutputReceiverBase) -> None: r""" @brief Specifies output for the tiling processor @@ -51553,7 +51838,7 @@ class Trans: """ @overload @classmethod - def new(cls, c: Trans, x: int, y: int) -> Trans: + def new(cls, c: Trans, x: Optional[int] = ..., y: Optional[int] = ...) -> Trans: r""" @brief Creates a transformation from another transformation plus a displacement @@ -51575,7 +51860,7 @@ class Trans: """ @overload @classmethod - def new(cls, rot: int, mirr: Optional[bool] = ..., u: Optional[Vector] = ...) -> Trans: + def new(cls, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., u: Optional[Vector] = ...) -> Trans: r""" @brief Creates a transformation using angle and mirror flag @@ -51588,7 +51873,7 @@ class Trans: """ @overload @classmethod - def new(cls, rot: int, mirr: bool, x: int, y: int) -> Trans: + def new(cls, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> Trans: r""" @brief Creates a transformation using angle and mirror flag and two coordinate values for displacement @@ -51654,7 +51939,7 @@ class Trans: @param u The Additional displacement """ @overload - def __init__(self, c: Trans, x: int, y: int) -> None: + def __init__(self, c: Trans, x: Optional[int] = ..., y: Optional[int] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a displacement @@ -51674,7 +51959,7 @@ class Trans: This constructor has been introduced in version 0.25 and replaces the previous static method 'from_dtrans'. """ @overload - def __init__(self, rot: int, mirr: Optional[bool] = ..., u: Optional[Vector] = ...) -> None: + def __init__(self, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., u: Optional[Vector] = ...) -> None: r""" @brief Creates a transformation using angle and mirror flag @@ -51686,7 +51971,7 @@ class Trans: @param u The displacement """ @overload - def __init__(self, rot: int, mirr: bool, x: int, y: int) -> None: + def __init__(self, rot: Optional[int] = ..., mirrx: Optional[bool] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> None: r""" @brief Creates a transformation using angle and mirror flag and two coordinate values for displacement @@ -52202,12 +52487,16 @@ class TrapezoidDecompositionMode: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -52219,6 +52508,10 @@ class TrapezoidDecompositionMode: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: TrapezoidDecompositionMode) -> bool: r""" @@ -52232,12 +52525,12 @@ class TrapezoidDecompositionMode: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ def __repr__(self) -> str: r""" @@ -52309,6 +52602,10 @@ class TrapezoidDecompositionMode: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -52511,12 +52808,16 @@ class VAlign: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -52528,6 +52829,10 @@ class VAlign: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: VAlign) -> bool: r""" @@ -52541,12 +52846,12 @@ class VAlign: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ def __repr__(self) -> str: r""" @@ -52618,6 +52923,10 @@ class VAlign: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -52766,7 +53075,7 @@ class VCplxTrans: """ @overload @classmethod - def new(cls, c: VCplxTrans, m: Optional[float] = ..., u: Optional[Vector] = ...) -> VCplxTrans: + def new(cls, c: VCplxTrans, mag: Optional[float] = ..., u: Optional[Vector] = ...) -> VCplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -52779,7 +53088,7 @@ class VCplxTrans: """ @overload @classmethod - def new(cls, c: VCplxTrans, m: float, x: float, y: float) -> VCplxTrans: + def new(cls, c: VCplxTrans, mag: Optional[float] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> VCplxTrans: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -52793,15 +53102,7 @@ class VCplxTrans: """ @overload @classmethod - def new(cls, m: float) -> VCplxTrans: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, u: Vector) -> VCplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[Vector] = ...) -> VCplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -52815,7 +53116,7 @@ class VCplxTrans: """ @overload @classmethod - def new(cls, mag: float, rot: float, mirrx: bool, x: int, y: int) -> VCplxTrans: + def new(cls, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> VCplxTrans: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -52830,15 +53131,7 @@ class VCplxTrans: """ @overload @classmethod - def new(cls, t: DTrans) -> VCplxTrans: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - @classmethod - def new(cls, t: DTrans, m: float) -> VCplxTrans: + def new(cls, t: DTrans, mag: Optional[float] = ...) -> VCplxTrans: r""" @brief Creates a transformation from a simple transformation and a magnification @@ -52846,21 +53139,33 @@ class VCplxTrans: """ @overload @classmethod - def new(cls, trans: CplxTrans) -> VCplxTrans: + def new(cls, trans: CplxTrans, dbu: Optional[float] = ...) -> VCplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates a floating-point to integer coordinate transformation from another coordinate flavour + + The 'dbu' argument is used to transform the input and output space from floating-point units to integer units and vice versa. Formally, the VCplxTrans transformation is initialized with 'to_dbu * trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: DCplxTrans) -> VCplxTrans: + def new(cls, trans: DCplxTrans, dbu: Optional[float] = ...) -> VCplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates a floating-point to integer coordinate transformation from another coordinate flavour + + The 'dbu' argument is used to transform the output space from floating-point units to integer units. Formally, the VCplxTrans transformation is initialized with 'to_dbu * trans' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + The 'dbu' argument has been added in version 0.29. """ @overload @classmethod - def new(cls, trans: ICplxTrans) -> VCplxTrans: + def new(cls, trans: ICplxTrans, dbu: Optional[float] = ...) -> VCplxTrans: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates a floating-point to integer coordinate transformation from another coordinate flavour + + The 'dbu' argument is used to transform the input and output space from floating-point units to integer units and vice versa. Formally, the VCplxTrans transformation is initialized with 'trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + The 'dbu' argument has been added in version 0.29. """ @overload @classmethod @@ -52909,7 +53214,7 @@ class VCplxTrans: @brief Creates a unit transformation """ @overload - def __init__(self, c: VCplxTrans, m: Optional[float] = ..., u: Optional[Vector] = ...) -> None: + def __init__(self, c: VCplxTrans, mag: Optional[float] = ..., u: Optional[Vector] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -52921,7 +53226,7 @@ class VCplxTrans: @param u The Additional displacement """ @overload - def __init__(self, c: VCplxTrans, m: float, x: float, y: float) -> None: + def __init__(self, c: VCplxTrans, mag: Optional[float] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> None: r""" @brief Creates a transformation from another transformation plus a magnification and displacement @@ -52934,14 +53239,7 @@ class VCplxTrans: @param y The Additional displacement (y) """ @overload - def __init__(self, m: float) -> None: - r""" - @brief Creates a transformation from a magnification - - Creates a magnifying transformation without displacement and rotation given the magnification m. - """ - @overload - def __init__(self, mag: float, rot: float, mirrx: bool, u: Vector) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., u: Optional[Vector] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -52954,7 +53252,7 @@ class VCplxTrans: @param u The displacement """ @overload - def __init__(self, mag: float, rot: float, mirrx: bool, x: int, y: int) -> None: + def __init__(self, mag: Optional[float] = ..., rot: Optional[float] = ..., mirrx: Optional[bool] = ..., x: Optional[int] = ..., y: Optional[int] = ...) -> None: r""" @brief Creates a transformation using magnification, angle, mirror flag and displacement @@ -52968,33 +53266,38 @@ class VCplxTrans: @param y The y displacement """ @overload - def __init__(self, t: DTrans) -> None: - r""" - @brief Creates a transformation from a simple transformation alone - - Creates a magnifying transformation from a simple transformation and a magnification of 1.0. - """ - @overload - def __init__(self, t: DTrans, m: float) -> None: + def __init__(self, t: DTrans, mag: Optional[float] = ...) -> None: r""" @brief Creates a transformation from a simple transformation and a magnification Creates a magnifying transformation from a simple transformation and a magnification. """ @overload - def __init__(self, trans: CplxTrans) -> None: + def __init__(self, trans: CplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates a floating-point to integer coordinate transformation from another coordinate flavour + + The 'dbu' argument is used to transform the input and output space from floating-point units to integer units and vice versa. Formally, the VCplxTrans transformation is initialized with 'to_dbu * trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: DCplxTrans) -> None: + def __init__(self, trans: DCplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates a floating-point to integer coordinate transformation from another coordinate flavour + + The 'dbu' argument is used to transform the output space from floating-point units to integer units. Formally, the VCplxTrans transformation is initialized with 'to_dbu * trans' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + The 'dbu' argument has been added in version 0.29. """ @overload - def __init__(self, trans: ICplxTrans) -> None: + def __init__(self, trans: ICplxTrans, dbu: Optional[float] = ...) -> None: r""" - @brief Creates a floating-point coordinate transformation from another coordinate flavour + @brief Creates a floating-point to integer coordinate transformation from another coordinate flavour + + The 'dbu' argument is used to transform the input and output space from floating-point units to integer units and vice versa. Formally, the VCplxTrans transformation is initialized with 'trans * to_dbu' where 'to_dbu' is the transformation into DBU space, or more precisely 'VCplxTrans(mag=1/dbu)'. + + The 'dbu' argument has been added in version 0.29. """ @overload def __init__(self, u: Vector) -> None: @@ -53434,7 +53737,13 @@ class VCplxTrans: The database unit can be specified to translate the integer coordinate displacement in database units to a floating-point displacement in micron units. The displacement's' coordinates will be multiplied with the database unit. - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors and is ill-named. Instead of 'to_itrans' use the conversion constructor: + + @code + dtrans = RBA::DCplxTrans::new(vtrans, dbu) + @/code + + This method has been deprecated in version 0.29. """ def to_s(self, lazy: Optional[bool] = ..., dbu: Optional[float] = ...) -> str: r""" @@ -53444,11 +53753,17 @@ class VCplxTrans: The lazy and DBU arguments have been added in version 0.27.6. """ - def to_trans(self) -> ICplxTrans: + def to_trans(self, arg0: float) -> ICplxTrans: r""" @brief Converts the transformation to another transformation with integer input coordinates - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors and is ill-named. Instead of 'to_trans' use the conversion constructor: + + @code + itrans = RBA::ICplxTrans::new(vtrans, dbu) + @/code + + This method has been deprecated in version 0.29. """ def to_vtrans(self, dbu: Optional[float] = ...) -> CplxTrans: r""" @@ -53456,7 +53771,13 @@ class VCplxTrans: The database unit can be specified to translate the integer coordinate displacement in database units to an floating-point displacement in micron units. The displacement's' coordinates will be multiplied with the database unit. - This method has been introduced in version 0.25. + This method is redundant with the conversion constructors and is ill-named. Instead of 'to_vtrans' use the conversion constructor: + + @code + trans = RBA::CplxTrans::new(vtrans, dbu) + @/code + + This method has been deprecated in version 0.29. """ @overload def trans(self, box: DBox) -> Box: @@ -53961,6 +54282,10 @@ class ZeroDistanceMode: r""" @brief Compares an enum with an integer value """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -53971,6 +54296,10 @@ class ZeroDistanceMode: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: ZeroDistanceMode) -> bool: r""" @@ -54061,6 +54390,10 @@ class ZeroDistanceMode: r""" @brief Creates a copy of self """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string diff --git a/src/pymod/distutils_src/klayout/laycore.pyi b/src/pymod/distutils_src/klayout/laycore.pyi index f5e2077b4..9ca149db3 100644 --- a/src/pymod/distutils_src/klayout/laycore.pyi +++ b/src/pymod/distutils_src/klayout/laycore.pyi @@ -838,6 +838,13 @@ class Annotation(BasicAnnotation): This constant has been introduced in version 0.25 """ + RulerModeAutoMetricEdge: ClassVar[int] + r""" + @brief Specifies edge-sensitive auto-metric ruler mode for the \register_template method + In auto-metric mode, a ruler can be placed with a single click and p1/p2 will be determined from the edge it is placed on. + + This constant has been introduced in version 0.29 + """ RulerModeNormal: ClassVar[int] r""" @brief Specifies normal ruler mode for the \register_template method @@ -4249,7 +4256,7 @@ class LayerProperties: This method has been introduced in version 0.22. """ @overload - def lower_hier_level_mode(self, arg0: bool) -> int: + def lower_hier_level_mode(self, real: bool) -> int: r""" @brief Gets the mode for the lower hierarchy level. @param real If true, the computed value is returned, otherwise the local node value @@ -4994,6 +5001,10 @@ class LayoutViewBase: r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -5004,6 +5015,10 @@ class LayoutViewBase: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: LayoutViewBase.SelectionMode) -> bool: r""" @@ -5032,6 +5047,10 @@ class LayoutViewBase: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -6364,6 +6383,13 @@ class LayoutViewBase: This method returns true, if self is a const reference. In that case, only const methods may be called on self. """ + def is_dirty(self) -> bool: + r""" + @brief Gets a flag indicating whether one of the layouts displayed needs saving + A layout is 'dirty' if it is modified and needs saving. This method returns true if this is the case for at least one of the layouts shown in the view. + + This method has been introduced in version 0.29. + """ def is_editable(self) -> bool: r""" @brief Returns true if the view is in editable mode @@ -6681,7 +6707,7 @@ class LayoutViewBase: See \set_title and \title for a description about how titles are handled. """ - def resize(self, arg0: int, arg1: int) -> None: + def resize(self, w: int, h: int) -> None: r""" @brief Resizes the layout view to the given dimension @@ -7062,7 +7088,7 @@ class LayoutViewBase: It is very important to stop the redraw thread before applying changes to the layout or the cell views and the LayoutView configuration. This is usually done automatically. For rare cases, where this is not the case, this method is provided. """ - def switch_mode(self, arg0: str) -> None: + def switch_mode(self, mode: str) -> None: r""" @brief Switches the mode. @@ -7204,12 +7230,16 @@ class Macro: @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares two enums + @brief Compares an enum with an integer value """ @overload def __eq__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer value + @brief Compares two enums + """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum """ @overload def __init__(self, i: int) -> None: @@ -7221,6 +7251,10 @@ class Macro: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: Macro.Format) -> bool: r""" @@ -7249,6 +7283,10 @@ class Macro: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string @@ -7308,6 +7346,10 @@ class Macro: r""" @brief Compares two enums """ + def __hash__(self) -> int: + r""" + @brief Gets the hash value from the enum + """ @overload def __init__(self, i: int) -> None: r""" @@ -7318,6 +7360,10 @@ class Macro: r""" @brief Creates an enum from a string value """ + def __int__(self) -> int: + r""" + @brief Gets the integer value from the enum + """ @overload def __lt__(self, other: Macro.Interpreter) -> bool: r""" @@ -7331,12 +7377,12 @@ class Macro: @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares two enums for inequality + @brief Compares an enum with an integer for inequality """ @overload def __ne__(self, other: object) -> bool: r""" - @brief Compares an enum with an integer for inequality + @brief Compares two enums for inequality """ def __repr__(self) -> str: r""" @@ -7346,6 +7392,10 @@ class Macro: r""" @brief Gets the symbolic string from an enum """ + def hash(self) -> int: + r""" + @brief Gets the hash value from the enum + """ def inspect(self) -> str: r""" @brief Converts an enum to a visual string diff --git a/src/pymod/distutils_src/klayout/tlcore.pyi b/src/pymod/distutils_src/klayout/tlcore.pyi index ae853b477..f821e70bf 100644 --- a/src/pymod/distutils_src/klayout/tlcore.pyi +++ b/src/pymod/distutils_src/klayout/tlcore.pyi @@ -308,7 +308,7 @@ class ArgType: r""" @brief Creates a copy of self """ - def __eq__(self, arg0: object) -> bool: + def __eq__(self, other: object) -> bool: r""" @brief Equality of two types """ @@ -316,7 +316,7 @@ class ArgType: r""" @brief Creates a new object of this class """ - def __ne__(self, arg0: object) -> bool: + def __ne__(self, other: object) -> bool: r""" @brief Inequality of two types """ @@ -1423,6 +1423,20 @@ class Method: r""" @brief Creates a new object of this class """ + def __repr__(self) -> str: + r""" + @brief Describes the method + This attribute returns a string description of the method and its signature. + + This method has been introduced in version 0.29. + """ + def __str__(self) -> str: + r""" + @brief Describes the method + This attribute returns a string description of the method and its signature. + + This method has been introduced in version 0.29. + """ def _create(self) -> None: r""" @brief Ensures the C++ object is created @@ -1460,7 +1474,7 @@ class Method: Usually it's not required to call this method. It has been introduced in version 0.24. """ - def accepts_num_args(self, arg0: int) -> bool: + def accepts_num_args(self, n: int) -> bool: r""" @brief True, if this method is compatible with the given number of arguments @@ -1569,6 +1583,13 @@ class Method: r""" @brief The return type of this method """ + def to_s(self) -> str: + r""" + @brief Describes the method + This attribute returns a string description of the method and its signature. + + This method has been introduced in version 0.29. + """ class MethodOverload: r""" diff --git a/src/pymod/pymod.pri b/src/pymod/pymod.pri index 7670517de..e030d0528 100644 --- a/src/pymod/pymod.pri +++ b/src/pymod/pymod.pri @@ -84,6 +84,8 @@ INSTALLS = lib_target QMAKE_POST_LINK += && $(MKDIR) $$DESTDIR_PYMOD/$$REALMODULE && $(COPY) $$PWD/distutils_src/klayout/$$REALMODULE/*.py $$DESTDIR_PYMOD/$$REALMODULE } + POST_TARGETDEPS += $$files($$PWD/distutils_src/klayout/$$REALMODULE/*.py, false) + # INSTALLS needs to be inside a lib or app templates. modsrc_target.path = $$PREFIX/pymod/klayout/$$REALMODULE # This would be nice: diff --git a/src/tl/tl/tl.pro b/src/tl/tl/tl.pro index 5a9f73562..7d1cf0c15 100644 --- a/src/tl/tl/tl.pro +++ b/src/tl/tl/tl.pro @@ -55,7 +55,8 @@ SOURCES = \ tlEquivalenceClusters.cc \ tlUniqueName.cc \ tlRecipe.cc \ - tlEnv.cc + tlEnv.cc \ + tlOptional.cc HEADERS = \ tlAlgorithm.h \ @@ -121,7 +122,8 @@ HEADERS = \ tlUniqueName.h \ tlRecipe.h \ tlSelect.h \ - tlEnv.h + tlEnv.h \ + tlOptional.h equals(HAVE_GIT2, "1") { diff --git a/src/tl/tl/tlOptional.cc b/src/tl/tl/tlOptional.cc new file mode 100644 index 000000000..ba2774439 --- /dev/null +++ b/src/tl/tl/tlOptional.cc @@ -0,0 +1,30 @@ +/* + + KLayout Layout Viewer + Copyright (C) 2006-2024 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "tlOptional.h" + +namespace tl +{ + +extern const nullopt_t nullopt = nullopt_t (); + +} // namespace tl diff --git a/src/tl/tl/tlOptional.h b/src/tl/tl/tlOptional.h new file mode 100644 index 000000000..c30fa6679 --- /dev/null +++ b/src/tl/tl/tlOptional.h @@ -0,0 +1,156 @@ +/* + + KLayout Layout Viewer + Copyright (C) 2006-2024 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_tlOptional +#define HDR_tlOptional + +#include "tlAssert.h" +#include "tlString.h" +#include "tlCommon.h" + +#include + +namespace tl +{ + +struct nullopt_t {}; + +extern const nullopt_t nullopt; + +/** + * @brief Poor man's partial implementation of C++17's std::optional + */ +template +class TL_PUBLIC_TEMPLATE optional +{ +public: + optional () : + m_value (), + m_is_valid (false) + {} + + optional (const nullopt_t &) : + m_value (), + m_is_valid (false) + {} + + optional (const T &value) : + m_value (value), + m_is_valid (true) + {} + + void reset () + { + m_is_valid = false; + } + + bool has_value() const { return m_is_valid; } + + T &value () + { + tl_assert (m_is_valid); + + return m_value; + } + + const T &value () const + { + tl_assert (m_is_valid); + + return m_value; + } + + T& operator* () + { + return value (); + } + + const T& operator* () const + { + return value (); + } + + T* operator-> () + { + return m_is_valid ? &m_value : 0; + } + + const T* operator-> () const + { + return m_is_valid ? &m_value : 0; + } + +private: + T m_value; + bool m_is_valid; +}; + +template +optional make_optional (const T &value) +{ + return optional (value); +} + +template +bool operator== (const optional &lhs, const optional &rhs) +{ + if (lhs.has_value () != rhs.has_value ()) { + return false; + } + if (!lhs.has_value ()) { + return true; + } + + return lhs.value() == rhs.value(); +} + +template +bool operator!= (const optional &lhs, const optional &rhs) +{ + return !(lhs == rhs); +} + +template +std::ostream &operator<< (std::ostream &ostr, const optional &rhs) +{ + if (rhs.has_value()) { + ostr << rhs.value(); + } else { + ostr << ""; + } + + return ostr; +} + +template +std::string to_string (const optional &opt) +{ + if (opt.has_value ()) { + return tl::to_string (*opt); + } else { + return std::string (); + } +} + +} // namespace tl + +#endif /* HDR_tlOptional */ diff --git a/src/tl/unit_tests/tlOptionalTests.cc b/src/tl/unit_tests/tlOptionalTests.cc new file mode 100644 index 000000000..182d0048d --- /dev/null +++ b/src/tl/unit_tests/tlOptionalTests.cc @@ -0,0 +1,97 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2024 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "tlOptional.h" +#include "tlUnitTest.h" + +namespace +{ + +TEST(1_Basic) +{ + tl::optional opt; + + // value not set + + EXPECT_EQ (opt.has_value (), false); + EXPECT_EQ (opt.operator-> (), (int *) 0); + EXPECT_EQ (((const tl::optional &) opt).operator-> (), (const int *) 0); + EXPECT_EQ (tl::to_string (opt), ""); + + try { + opt.value (); // asserts + EXPECT_EQ (true, false); + } catch (...) { + } + + // make_optional, assignment + + opt = tl::make_optional (17); + + // value set + + EXPECT_EQ (opt.has_value (), true); + EXPECT_EQ (opt.value (), 17); + EXPECT_EQ (tl::to_string (opt), "17"); + EXPECT_EQ (((const tl::optional &) opt).value (), 17); + EXPECT_EQ (*opt, 17); + EXPECT_EQ (*((const tl::optional &) opt), 17); + EXPECT_EQ (*(opt.operator-> ()), 17); + EXPECT_EQ (*(((const tl::optional &) opt).operator-> ()), 17); + + // compare operators + + EXPECT_EQ (opt == tl::make_optional (-1), false); + EXPECT_EQ (opt == tl::make_optional (17), true); + EXPECT_EQ (opt == tl::optional (), false); + + EXPECT_EQ (opt != tl::make_optional (-1), true); + EXPECT_EQ (opt != tl::make_optional (17), false); + EXPECT_EQ (opt != tl::optional (), true); + + // copy ctor + + tl::optional opt2 (opt); + + EXPECT_EQ (opt2.has_value (), true); + EXPECT_EQ (opt2.value (), 17); + + // reset method + + opt = tl::make_optional (17); + opt.reset (); + + EXPECT_EQ (opt.has_value (), false); + EXPECT_EQ (opt == tl::optional (), true); + EXPECT_EQ (opt != tl::optional (), false); + + // tl::nullopt tag + + opt = tl::make_optional (17); + opt = tl::optional (tl::nullopt); + + EXPECT_EQ (opt.has_value (), false); + EXPECT_EQ (opt == tl::optional (), true); + EXPECT_EQ (opt != tl::optional (), false); +} + +} diff --git a/src/tl/unit_tests/unit_tests.pro b/src/tl/unit_tests/unit_tests.pro index 0e4bcc449..3d8b06676 100644 --- a/src/tl/unit_tests/unit_tests.pro +++ b/src/tl/unit_tests/unit_tests.pro @@ -31,6 +31,7 @@ SOURCES = \ tlLongIntTests.cc \ tlMathTests.cc \ tlObjectTests.cc \ + tlOptionalTests.cc \ tlPixelBufferTests.cc \ tlResourcesTests.cc \ tlReuseVectorTests.cc \ diff --git a/testdata/ruby/dbPCells.rb b/testdata/ruby/dbPCells.rb index ac96c68d3..d65150618 100644 --- a/testdata/ruby/dbPCells.rb +++ b/testdata/ruby/dbPCells.rb @@ -190,6 +190,78 @@ def norm_hash(hash) end +class DBPCellAPI_TestClass < TestBase + + def test_1 + + # PCellParameterDeclaration + + decl = RBA::PCellParameterDeclaration::new("name", RBA::PCellParameterDeclaration::TypeString, "description") + + assert_equal(decl.name, "name") + assert_equal(decl.description, "description") + assert_equal(decl.default.inspect, "nil") + assert_equal(decl.unit, "") + assert_equal(decl.type, RBA::PCellParameterDeclaration::TypeString) + + decl = RBA::PCellParameterDeclaration::new("name", RBA::PCellParameterDeclaration::TypeString, "description", "17") + + assert_equal(decl.name, "name") + assert_equal(decl.description, "description") + assert_equal(decl.type, RBA::PCellParameterDeclaration::TypeString) + assert_equal(decl.default.to_s, "17") + assert_equal(decl.unit, "") + + decl = RBA::PCellParameterDeclaration::new("name", RBA::PCellParameterDeclaration::TypeString, "description", "17", "unit") + + assert_equal(decl.name, "name") + assert_equal(decl.description, "description") + assert_equal(decl.type, RBA::PCellParameterDeclaration::TypeString) + assert_equal(decl.default.to_s, "17") + assert_equal(decl.unit, "unit") + + decl.name = "n" + assert_equal(decl.name, "n") + decl.description = "d" + assert_equal(decl.description, "d") + decl.unit = "u" + assert_equal(decl.unit, "u") + decl.type = RBA::PCellParameterDeclaration::TypeBoolean + assert_equal(decl.type, RBA::PCellParameterDeclaration::TypeBoolean) + decl.default = true + assert_equal(decl.default.to_s, "true") + + decl.type = RBA::PCellParameterDeclaration::TypeInt + assert_equal(decl.min_value.inspect, "nil") + assert_equal(decl.max_value.inspect, "nil") + decl.min_value = "-1" + assert_equal(decl.min_value.to_s, "-1") + decl.max_value = "42" + assert_equal(decl.max_value.to_s, "42") + decl.min_value = nil + decl.max_value = nil + assert_equal(decl.min_value.inspect, "nil") + assert_equal(decl.max_value.inspect, "nil") + + assert_equal(decl.hidden?, false) + decl.hidden = true + assert_equal(decl.hidden?, true) + + assert_equal(decl.readonly?, false) + decl.readonly = true + assert_equal(decl.readonly?, true) + + decl.add_choice("first", 42) + assert_equal(decl.choice_values, [42]) + assert_equal(decl.choice_descriptions, ["first"]) + decl.clear_choices + assert_equal(decl.choice_values, []) + assert_equal(decl.choice_descriptions, []) + + end + +end + class DBPCell_TestClass < TestBase def test_1 From ea645b7cf07d7009e35ea8a94df06acd491c6307 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 Mar 2024 00:53:55 +0100 Subject: [PATCH 56/63] Enabling Python 3.12 for Windows for PyPI. Needs a new release --- azure-pipelines.yml | 6 ++++++ version.sh | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a4061e230..780263f47 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,6 +32,9 @@ jobs: cp311-cp311-win_amd64.whl: python.version: '3.11' python.architecture: 'x64' + cp312-cp312-win_amd64.whl: + python.version: '3.12' + python.architecture: 'x64' cp36-cp36m-win32.whl: python.version: '3.6' python.architecture: 'x86' @@ -50,6 +53,9 @@ jobs: cp311-cp311-win32.whl: python.version: '3.11' python.architecture: 'x86' + cp312-cp312-win32.whl: + python.version: '3.12' + python.architecture: 'x86' maxParallel: 6 steps: diff --git a/version.sh b/version.sh index 9163a6f1d..efaa36f03 100644 --- a/version.sh +++ b/version.sh @@ -5,7 +5,7 @@ KLAYOUT_VERSION="0.28.17" # The version used for PyPI (don't use variables here!) -KLAYOUT_PYPI_VERSION="0.28.17" +KLAYOUT_PYPI_VERSION="0.28.17-1" # The build date KLAYOUT_VERSION_DATE=$(date "+%Y-%m-%d") From 6faf3335882e4c289e9f59584572da509f0eae22 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 Mar 2024 18:24:31 +0100 Subject: [PATCH 57/63] Include deployment of Python 3.12 support for klayout PyPI module --- azure-pipelines.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 780263f47..fe6cbc813 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -136,6 +136,11 @@ jobs: vmImage: 'windows-2019' # other options: 'macOS-10.13', 'ubuntu-16.04' steps: - checkout: none #skip checking out the default repository resource + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifacts wheel-3.12.x64' + inputs: + artifactName: 'wheel-3.12.x64' + downloadPath: '$(System.DefaultWorkingDirectory)' - task: DownloadBuildArtifacts@0 displayName: 'Download Build Artifacts wheel-3.11.x64' inputs: @@ -166,6 +171,11 @@ jobs: inputs: artifactName: 'wheel-3.6.x64' downloadPath: '$(System.DefaultWorkingDirectory)' + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifacts wheel-3.12.x86' + inputs: + artifactName: 'wheel-3.12.x86' + downloadPath: '$(System.DefaultWorkingDirectory)' - task: DownloadBuildArtifacts@0 displayName: 'Download Build Artifacts wheel-3.11.x86' inputs: From b4d90ef94ced1c3635ce4e2856ff7078c3718501 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 20 Mar 2024 00:08:47 +0100 Subject: [PATCH 58/63] New version for future release --- version.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.sh b/version.sh index efaa36f03..43b2ae5b6 100644 --- a/version.sh +++ b/version.sh @@ -2,10 +2,10 @@ # This script is sourced to define the main version parameters # The main version -KLAYOUT_VERSION="0.28.17" +KLAYOUT_VERSION="0.29.0" # The version used for PyPI (don't use variables here!) -KLAYOUT_PYPI_VERSION="0.28.17-1" +KLAYOUT_PYPI_VERSION="0.29.0" # The build date KLAYOUT_VERSION_DATE=$(date "+%Y-%m-%d") From 9e81a2f2aff63b52ca02015af44137dd848e712a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 23 Mar 2024 15:41:07 +0100 Subject: [PATCH 59/63] Added a dummy Changelog to make Debian builds pass --- Changelog.Debian | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.Debian b/Changelog.Debian index 8a6a53a23..735a1aaae 100644 --- a/Changelog.Debian +++ b/Changelog.Debian @@ -1,3 +1,10 @@ +klayout (0.29.0-1) unstable; urgency=low + + * New features and bugfixes + - See changelog + + -- Matthias Köfferlein Fri, 01 Apr 2024 12:00:00 +0100 + klayout (0.28.17-1) unstable; urgency=low * New features and bugfixes From b962514767602c0e173eea83a68b6778e02f8cc4 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Sat, 23 Mar 2024 10:44:04 -0400 Subject: [PATCH 60/63] Add include needed for git_error_set_str for libgit2>=1.8 (#1658) `git_error_set_str` was moved into the `sys` subdirectory in libgit2 1.8.0. See [this pull request](https://github.com/libgit2/libgit2/pull/6625) for details and [this issue](https://github.com/libgit2/libgit2/issues/6776) for more context. --- src/tl/tl/tlGit.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tl/tl/tlGit.cc b/src/tl/tl/tlGit.cc index 3e749e633..6278bccfe 100644 --- a/src/tl/tl/tlGit.cc +++ b/src/tl/tl/tlGit.cc @@ -30,6 +30,9 @@ #include "tlEnv.h" #include +#if LIBGIT2_VER_MAJOR > 1 || (LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR >= 8) + #include +#endif #include namespace tl From 4163e3a52ce212e52f7cfac86f0b9192fab98b17 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 23 Mar 2024 16:59:24 +0100 Subject: [PATCH 61/63] Dummy Changelog --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index 5a9b54af4..2ac7bcff6 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +0.29.0 (2024-04-01): +* TODO + 0.28.17 (2024-02-16): * Enhancement: %GITHUB%/issues/1626 Technology specific grids From 54273206a750404c0f61bf4f6c721b27a9b0eb63 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 23 Mar 2024 20:18:38 +0100 Subject: [PATCH 62/63] More robust tests --- src/db/unit_tests/dbEdgesTests.cc | 64 +++++++++++++++---------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/db/unit_tests/dbEdgesTests.cc b/src/db/unit_tests/dbEdgesTests.cc index 97f0d890c..d9cb66589 100644 --- a/src/db/unit_tests/dbEdgesTests.cc +++ b/src/db/unit_tests/dbEdgesTests.cc @@ -1199,30 +1199,30 @@ TEST(30) db::Edges edup; - EXPECT_EQ (e.selected_interacting (e2).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (2)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (2), size_t(2)).to_string (), "(0,10;200,10)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (2), size_t(3)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (3)).to_string (), "(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (e2, size_t (4)).to_string (), ""); + EXPECT_EQ (db::compare (e.selected_interacting (e2), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (2)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (2), size_t(2)), "(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (2), size_t(3)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (3)), "(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (e2, size_t (4)), ""), true); edup = e; edup.select_interacting (e2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (edup, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); - EXPECT_EQ (e.selected_not_interacting (e2).to_string (), ""); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (2)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (2), size_t(2)).to_string (), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (2), size_t(3)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (3)).to_string (), "(0,0;200,0);(0,10;200,10)"); - EXPECT_EQ (e.selected_not_interacting (e2, size_t (4)).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2), ""), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (2)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (2), size_t(2)), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (2), size_t(3)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (3)), "(0,0;200,0);(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (e2, size_t (4)), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); edup = e; edup.select_not_interacting (e2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (edup, "(0,0;200,0)"), true); - EXPECT_EQ (e.selected_interacting_differential (e2, size_t (2), size_t(3)).first.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting_differential (e2, size_t (2), size_t(3)).second.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (e.selected_interacting_differential (e2, size_t (2), size_t(3)).first, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting_differential (e2, size_t (2), size_t(3)).second, "(0,0;200,0)"), true); db::Region r2; r2.insert (db::Box (db::Point (99, 0), db::Point (101, 10))); @@ -1231,30 +1231,30 @@ TEST(30) r2.insert (db::Box (db::Point (119, 19), db::Point (121, 21))); r2.insert (db::Box (db::Point (129, 29), db::Point (131, 31))); - EXPECT_EQ (e.selected_interacting (r2).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (2)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (2), size_t(2)).to_string (), "(0,10;200,10)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (2), size_t(3)).to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (3)).to_string (), "(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting (r2, size_t (4)).to_string (), ""); + EXPECT_EQ (db::compare (e.selected_interacting (r2), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (2)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (2), size_t(2)), "(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (2), size_t(3)), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (3)), "(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting (r2, size_t (4)), ""), true); edup = e; edup.select_interacting (r2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (edup, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); - EXPECT_EQ (e.selected_not_interacting (r2).to_string (), ""); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (2)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (2), size_t(2)).to_string (), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (2), size_t(3)).to_string (), "(0,0;200,0)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (3)).to_string (), "(0,0;200,0);(0,10;200,10)"); - EXPECT_EQ (e.selected_not_interacting (r2, size_t (4)).to_string (), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2), ""), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (2)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (2), size_t(2)), "(0,0;200,0);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (2), size_t(3)), "(0,0;200,0)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (3)), "(0,0;200,0);(0,10;200,10)"), true); + EXPECT_EQ (db::compare (e.selected_not_interacting (r2, size_t (4)), "(0,0;200,0);(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); edup = e; edup.select_not_interacting (r2, size_t (2), size_t(3)); - EXPECT_EQ (edup.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (edup, "(0,0;200,0)"), true); - EXPECT_EQ (e.selected_interacting_differential (r2, size_t (2), size_t(3)).first.to_string (), "(0,10;200,10);(0,20;200,20);(0,30;200,30)"); - EXPECT_EQ (e.selected_interacting_differential (r2, size_t (2), size_t(3)).second.to_string (), "(0,0;200,0)"); + EXPECT_EQ (db::compare (e.selected_interacting_differential (r2, size_t (2), size_t(3)).first, "(0,10;200,10);(0,20;200,20);(0,30;200,30)"), true); + EXPECT_EQ (db::compare (e.selected_interacting_differential (r2, size_t (2), size_t(3)).second, "(0,0;200,0)"), true); } // borrowed from deep edges tests From 97a33f8d66ad2af464de2e9f3823438b79415289 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 23 Mar 2024 20:28:41 +0100 Subject: [PATCH 63/63] Trying to fix a linker issue on MSYS --- src/tl/tl/tlStream.cc | 5 +++++ src/tl/tl/tlStream.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc index 4ffc2d43e..751c4841d 100644 --- a/src/tl/tl/tlStream.cc +++ b/src/tl/tl/tlStream.cc @@ -47,6 +47,7 @@ #include "tlException.h" #include "tlString.h" #include "tlUri.h" +#include "tlHttpStream.h" #if defined(HAVE_QT) # include @@ -259,6 +260,10 @@ inflating_input_stream::auto_detect_gz () return true; } +// explicit instantiations +template class inflating_input_stream; +template class inflating_input_stream; + // --------------------------------------------------------------- // InputStream implementation diff --git a/src/tl/tl/tlStream.h b/src/tl/tl/tlStream.h index 4e7cfdcf6..c738c84ff 100644 --- a/src/tl/tl/tlStream.h +++ b/src/tl/tl/tlStream.h @@ -590,7 +590,7 @@ private: * @brief A wrapper that adds generic .gz support */ template -class TL_PUBLIC_TEMPLATE inflating_input_stream +class TL_PUBLIC inflating_input_stream : public InputStreamBase { public: