Implemented #808 (Feature suggestion: DRC to report edges attached to corners as edge pairs). Solution is available for DRC layers and universal DRC expressions.

This commit is contained in:
Matthias Koefferlein 2021-05-29 17:57:38 +02:00
parent fd1e206c56
commit 5ceeafc0ff
16 changed files with 132 additions and 37 deletions

View File

@ -52,7 +52,7 @@ void CornerDetectorCore::detect_corners (const db::Polygon &poly, const CornerPo
db::Point pn = ctr [j]; db::Point pn = ctr [j];
if (m_checker (pt - pp, pn - pt)) { if (m_checker (pt - pp, pn - pt)) {
delivery.make_point (pt); delivery.make_point (pt, db::Edge (pp, pt), db::Edge (pt, pn));
} }
pp = pt; pp = pt;

View File

@ -41,7 +41,7 @@ namespace db
class DB_PUBLIC CornerPointDelivery class DB_PUBLIC CornerPointDelivery
{ {
public: public:
virtual void make_point (const db::Point &pt) const = 0; virtual void make_point (const db::Point &pt, const db::Edge &e1, const db::Edge &e2) const = 0;
}; };
/** /**
@ -55,7 +55,7 @@ public:
: m_d (dim, dim), mp_result (&result) : m_d (dim, dim), mp_result (&result)
{ } { }
virtual void make_point (const db::Point &pt) const virtual void make_point (const db::Point &pt, const db::Edge &, const db::Edge &) const
{ {
mp_result->push_back (db::Polygon (db::Box (pt - m_d, pt + m_d))); mp_result->push_back (db::Polygon (db::Box (pt - m_d, pt + m_d)));
} }
@ -76,7 +76,7 @@ public:
: mp_result (&result) : mp_result (&result)
{ } { }
virtual void make_point (const db::Point &pt) const virtual void make_point (const db::Point &pt, const db::Edge &, const db::Edge &) const
{ {
mp_result->push_back (db::Edge (pt, pt)); mp_result->push_back (db::Edge (pt, pt));
} }
@ -86,6 +86,26 @@ private:
std::vector<db::Edge> *mp_result; std::vector<db::Edge> *mp_result;
}; };
/**
* @brief An interface to accept corners and turns them into edge pairs
*/
class DB_PUBLIC CornerEdgePairDelivery
: public CornerPointDelivery
{
public:
CornerEdgePairDelivery (std::vector<db::EdgePair> &result)
: mp_result (&result)
{ }
virtual void make_point (const db::Point &, const db::Edge &e1, const db::Edge &e2) const
{
mp_result->push_back (db::EdgePair (e1, e2));
}
private:
std::vector<db::EdgePair> *mp_result;
};
/** /**
* @brief A helper class implementing the core corner detection algorithm * @brief A helper class implementing the core corner detection algorithm
*/ */
@ -155,6 +175,31 @@ public:
virtual bool wants_variants () const { return false; } virtual bool wants_variants () const { return false; }
}; };
/**
* @brief A corner detector delivering edge pairs for the corners
*/
class DB_PUBLIC CornersAsEdgePairs
: public db::PolygonToEdgePairProcessorBase, private CornerDetectorCore
{
public:
CornersAsEdgePairs (double angle_start, bool include_angle_start, double angle_end, bool include_angle_end)
: CornerDetectorCore (angle_start, include_angle_start, angle_end, include_angle_end)
{
// .. nothing yet ..
}
void process (const db::Polygon &poly, std::vector<db::EdgePair> &result) const
{
detect_corners (poly, CornerEdgePairDelivery (result));
}
virtual const TransformationReducer *vars () const { return 0; }
virtual bool result_is_merged () const { return false; }
virtual bool result_must_not_be_merged () const { return true; } // to preserve dots
virtual bool requires_raw_input () const { return false; }
virtual bool wants_variants () const { return false; }
};
// ----------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------
// Extents // Extents

View File

@ -226,6 +226,12 @@ static db::CompoundRegionOperationNode *new_corners_as_dots (db::CompoundRegionO
return new db::CompoundRegionToEdgeProcessingOperationNode (new db::CornersAsDots (angle_start, include_angle_start, angle_end, include_angle_end), input, true /*processor is owned*/); return new db::CompoundRegionToEdgeProcessingOperationNode (new db::CornersAsDots (angle_start, include_angle_start, angle_end, include_angle_end), input, true /*processor is owned*/);
} }
static db::CompoundRegionOperationNode *new_corners_as_edge_pairs (db::CompoundRegionOperationNode *input, double angle_start, bool include_angle_start, double angle_end, bool include_angle_end)
{
check_non_null (input, "input");
return new db::CompoundRegionToEdgePairProcessingOperationNode (new db::CornersAsEdgePairs (angle_start, include_angle_start, angle_end, include_angle_end), input, true /*processor is owned*/);
}
static db::CompoundRegionOperationNode *new_extents (db::CompoundRegionOperationNode *input, db::Coord e) static db::CompoundRegionOperationNode *new_extents (db::CompoundRegionOperationNode *input, db::Coord e)
{ {
check_non_null (input, "input"); check_non_null (input, "input");
@ -603,6 +609,12 @@ Class<db::CompoundRegionOperationNode> decl_CompoundRegionOperationNode ("db", "
gsi::constructor ("new_corners_as_dots", &new_corners_as_dots, gsi::arg ("input"), gsi::arg ("angle_min"), gsi::arg ("include_angle_min"), gsi::arg ("angle_max"), gsi::arg ("include_angle_max"), gsi::constructor ("new_corners_as_dots", &new_corners_as_dots, gsi::arg ("input"), gsi::arg ("angle_min"), gsi::arg ("include_angle_min"), gsi::arg ("angle_max"), gsi::arg ("include_angle_max"),
"@brief Creates a node turning corners into dots (single-point edges).\n" "@brief Creates a node turning corners into dots (single-point edges).\n"
) + ) +
gsi::constructor ("new_corners_as_edge_pairs", &new_corners_as_edge_pairs, gsi::arg ("input"), gsi::arg ("angle_min"), gsi::arg ("include_angle_min"), gsi::arg ("angle_max"), gsi::arg ("include_angle_max"),
"@brief Creates a node turning corners into edge pairs containing the two edges adjacent to the corner.\n"
"The first edge will be the incoming edge and the second one the outgoing edge.\n"
"\n"
"This feature has been introduced in version 0.27.1.\n"
) +
gsi::constructor ("new_extents", &new_extents, gsi::arg ("input"), gsi::arg ("e", 0), gsi::constructor ("new_extents", &new_extents, gsi::arg ("input"), gsi::arg ("e", 0),
"@brief Creates a node returning the extents of the objects.\n" "@brief Creates a node returning the extents of the objects.\n"
"The 'e' parameter provides a generic enlargement which is applied to the boxes. This is helpful to cover dot-like edges or edge pairs in the input." "The 'e' parameter provides a generic enlargement which is applied to the boxes. This is helpful to cover dot-like edges or edge pairs in the input."

View File

@ -123,6 +123,11 @@ static db::Region corners_to_boxes (const db::Region *r, double angle_start, dou
return r->processed (db::CornersAsRectangles (angle_start, include_angle_start, angle_end, include_angle_end, dim)); return r->processed (db::CornersAsRectangles (angle_start, include_angle_start, angle_end, include_angle_end, dim));
} }
static db::EdgePairs corners_to_edge_pairs (const db::Region *r, double angle_start, double angle_end, bool include_angle_start, bool include_angle_end)
{
return r->processed (db::CornersAsEdgePairs (angle_start, include_angle_start, angle_end, include_angle_end));
}
static db::Region *new_si (const db::RecursiveShapeIterator &si) static db::Region *new_si (const db::RecursiveShapeIterator &si)
{ {
return new db::Region (si); return new db::Region (si);
@ -1373,14 +1378,22 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n" "\n"
"A similar function that reports corners as point-like edges is \\corners_dots.\n" "A similar function that reports corners as point-like edges is \\corners_dots.\n"
"\n" "\n"
"This function has been introduced in version 0.25. 'include_min_angle' and 'include_max_angle' have been added in version 0.27.\n" "This method has been introduced in version 0.25. 'include_min_angle' and 'include_max_angle' have been added in version 0.27.\n"
) + ) +
method_ext ("corners_dots", &corners_to_dots, gsi::arg ("angle_start", -180.0), gsi::arg ("angle_end", 180.0), gsi::arg ("include_min_angle", true), gsi::arg ("include_max_angle", true), method_ext ("corners_dots", &corners_to_dots, gsi::arg ("angle_start", -180.0), gsi::arg ("angle_end", 180.0), gsi::arg ("include_min_angle", true), gsi::arg ("include_max_angle", true),
"@brief This method will select all corners whose attached edges satisfy the angle condition.\n" "@brief This method will select all corners whose attached edges satisfy the angle condition.\n"
"\n" "\n"
"This method is similar to \\corners, but delivers an \\Edges collection with dot-like edges for each corner.\n" "This method is similar to \\corners, but delivers an \\Edges collection with dot-like edges for each corner.\n"
"\n" "\n"
"This function has been introduced in version 0.25. 'include_min_angle' and 'include_max_angle' have been added in version 0.27.\n" "This method has been introduced in version 0.25. 'include_min_angle' and 'include_max_angle' have been added in version 0.27.\n"
) +
method_ext ("corners_edge_pairs", &corners_to_edge_pairs, gsi::arg ("angle_start", -180.0), gsi::arg ("angle_end", 180.0), gsi::arg ("include_min_angle", true), gsi::arg ("include_max_angle", true),
"@brief This method will select all corners whose attached edges satisfy the angle condition.\n"
"\n"
"This method is similar to \\corners, but delivers an \\EdgePairs collection with an edge pairs for each corner.\n"
"The first edge is the incoming edge of the corner, the second one the outgoing edge.\n"
"\n"
"This method has been introduced in version 0.27.1.\n"
) + ) +
method ("merge", (db::Region &(db::Region::*) ()) &db::Region::merge, method ("merge", (db::Region &(db::Region::*) ()) &db::Region::merge,
"@brief Merge the region\n" "@brief Merge the region\n"

View File

@ -787,14 +787,14 @@ CODE
# The "corners" method is available as a plain function or as a method on \DRC# expressions. # The "corners" method is available as a plain function or as a method on \DRC# expressions.
# The plain function is equivalent to "primary.corners". # The plain function is equivalent to "primary.corners".
def corners(as_dots = DRCAsDots::new(false)) def corners(output_mode = DRCOutputMode::new(:dots))
@engine._context("corners") do @engine._context("corners") do
if as_dots.is_a?(DRCAsDots) if output_mode.is_a?(DRCOutputMode)
as_dots = as_dots.value output_mode = output_mode.value
else else
raise("Invalid argument (#{as_dots.inspect}) for 'corners' method") raise("Invalid argument (#{as_dots.inspect}) for 'corners' method")
end end
DRCOpNodeCornersFilter::new(@engine, as_dots, self) DRCOpNodeCornersFilter::new(@engine, output_mode, self)
end end
end end
@ -859,8 +859,8 @@ CODE
args.each_with_index do |a,ia| args.each_with_index do |a,ia|
if a.is_a?(1.0.class) && :#{f} != :middle if a.is_a?(1.0.class) && :#{f} != :middle
f << a f << a
elsif a.is_a?(DRCAsDots) elsif a.is_a?(DRCOutputMode)
as_edges = a.value as_edges = (a.value == :edges || a.value == :dots)
elsif @@std_refs[a] && :#{f} != :middle elsif @@std_refs[a] && :#{f} != :middle
f = @@std_refs[a] f = @@std_refs[a]
else else
@ -1996,11 +1996,11 @@ end
class DRCOpNodeCornersFilter < DRCOpNodeWithCompare class DRCOpNodeCornersFilter < DRCOpNodeWithCompare
attr_accessor :input attr_accessor :input
attr_accessor :as_dots attr_accessor :output_mode
def initialize(engine, as_dots, input) def initialize(engine, output_mode, input)
super(engine) super(engine)
self.as_dots = as_dots self.output_mode = output_mode
self.input = input self.input = input
self.description = "corners" self.description = "corners"
end end
@ -2011,8 +2011,10 @@ class DRCOpNodeCornersFilter < DRCOpNodeWithCompare
args << (self.gt ? false : true) args << (self.gt ? false : true)
args << (self.lt ? self.lt : (self.le ? self.le : 180.0)) args << (self.lt ? self.lt : (self.le ? self.le : 180.0))
args << (self.lt ? false : true) args << (self.lt ? false : true)
if self.as_dots if self.output_mode == :dots || self.output_mode == :edges
RBA::CompoundRegionOperationNode::new_corners_as_dots(*args) RBA::CompoundRegionOperationNode::new_corners_as_dots(*args)
elsif self.output_mode == :edge_pairs
RBA::CompoundRegionOperationNode::new_corners_as_edge_pairs(*args)
else else
args << 2 # dimension is 2x2 DBU args << 2 # dimension is 2x2 DBU
RBA::CompoundRegionOperationNode::new_corners_as_rectangles(*args) RBA::CompoundRegionOperationNode::new_corners_as_rectangles(*args)

View File

@ -781,7 +781,7 @@ CODE
# %DRC% # %DRC%
# @name corners # @name corners
# @brief Selects all polygons which are rectilinear # @brief Selects corners of polygons
# @synopsis corners([ options ]) (in condition) # @synopsis corners([ options ]) (in condition)
# @synopsis corners(layer [, options ]) # @synopsis corners(layer [, options ])
# #
@ -791,15 +791,16 @@ CODE
# \DRC# expressions (see \Layer#drc and \DRC#corners for more details). # \DRC# expressions (see \Layer#drc and \DRC#corners for more details).
# #
# Like the layer-based version, the "corners" operator accepts the # Like the layer-based version, the "corners" operator accepts the
# output type option: "as_dots" for dot-like edges and "as_boxes" for # output type option: "as_dots" for dot-like edges, "as_boxes" for
# small (2x2 DBU) box markers. # small (2x2 DBU) box markers and "as_edge_pairs" for edge pairs.
# The default output type is "as_boxes".
# #
# The "corners" operator can be put into a condition which means it's # The "corners" operator can be put into a condition which means it's
# applied to corners meeting a particular angle constraint. # applied to corners meeting a particular angle constraint.
def _cop_corners(as_dots = DRCAsDots::new(false)) def _cop_corners(output_mode = DRCOutputMode::new(:boxes))
# NOTE: this method is a fallback for the respective global ones which route to DRCLayer or here. # NOTE: this method is a fallback for the respective global ones which route to DRCLayer or here.
return primary.corners(as_dots) return primary.corners(output_mode)
end end
# %DRC% # %DRC%

View File

@ -226,15 +226,19 @@ module DRC
end end
def as_dots def as_dots
DRCAsDots::new(true) DRCOutputMode::new(:dots)
end end
def as_edges def as_edges
DRCAsDots::new(true) DRCOutputMode::new(:edges)
end end
def as_boxes def as_boxes
DRCAsDots::new(false) DRCOutputMode::new(:boxes)
end
def as_edge_pairs
DRCOutputMode::new(:edge_pairs)
end end
def area_only(r) def area_only(r)

View File

@ -1118,8 +1118,8 @@ CODE
elsif a.is_a?(DRCPattern) elsif a.is_a?(DRCPattern)
as_pattern = a.as_pattern as_pattern = a.as_pattern
pattern = a.pattern pattern = a.pattern
elsif a.is_a?(DRCAsDots) elsif a.is_a?(DRCOutputMode)
as_dots = a.value as_dots = (a.value == :edges || a.value == :dots)
else else
raise("Invalid argument type #{a.inspect}") raise("Invalid argument type #{a.inspect}")
end end
@ -1167,6 +1167,8 @@ CODE
# @ul # @ul
# @li @b as_boxes @/b: with this option, small boxes will be produced as markers @/li # @li @b as_boxes @/b: with this option, small boxes will be produced as markers @/li
# @li @b as_dots @/b: with this option, point-like edges will be produced instead of small boxes @/li # @li @b as_dots @/b: with this option, point-like edges will be produced instead of small boxes @/li
# @li @b as_edge_pairs @/b: with this option, an edge pair is produced for each corner selected. The first edge
# is the incoming edge to the corner, the second edge the outgoing edge. @/li
# @/ul # @/ul
# #
# The following images show the effect of this method: # The following images show the effect of this method:
@ -1185,7 +1187,7 @@ CODE
requires_region requires_region
as_dots = false output_mode = :boxes
amin = -180.0 amin = -180.0
amax = 180.0 amax = 180.0
@ -1199,14 +1201,23 @@ CODE
elsif a.is_a?(1.0.class) || a.is_a?(1.class) elsif a.is_a?(1.0.class) || a.is_a?(1.class)
amin = a.to_f amin = a.to_f
amax = a.to_f amax = a.to_f
elsif a.is_a?(DRCAsDots) elsif a.is_a?(DRCOutputMode)
as_dots = a.value output_mode = a.value
else else
raise("Invalid argument #{a.inspect}") raise("Invalid argument #{a.inspect}")
end end
end end
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, as_dots ? :corners_dots : :corners, amin, amax)) f = :corners
cls = RBA::Region
if output_mode == :edges || output_mode == :dots
f = :corners_dots
cls = RBA::Edges
elsif output_mode == :edge_pairs
f = :corners_edge_pairs
cls = RBA::EdgePairs
end
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, cls, f, amin, amax))
end end
@ -1375,8 +1386,8 @@ CODE
args.each do |a| args.each do |a|
if a.is_a?(1.0.class) && :#{f} != :middle if a.is_a?(1.0.class) && :#{f} != :middle
f << a f << a
elsif a.is_a?(DRCAsDots) elsif a.is_a?(DRCOutputMode)
as_edges = a.value as_edges = (a.value == :edges || a.value == :dots)
elsif @@std_refs[a] && :#{f} != :middle elsif @@std_refs[a] && :#{f} != :middle
f = @@std_refs[a] f = @@std_refs[a]
else else
@ -4736,7 +4747,7 @@ END
end end
def requires_edge_pairs def requires_edge_pairs
self.data.is_a?(RBA::EdgePairs) || raise("Requires a edge pair layer") self.data.is_a?(RBA::EdgePairs) || raise("Requires an edge pair layer")
end end
def requires_edges def requires_edges

View File

@ -77,7 +77,7 @@ module DRC
# A wrapper for the "as_dots" or "as_boxes" flag for # A wrapper for the "as_dots" or "as_boxes" flag for
# some DRC functions. The purpose of this class # some DRC functions. The purpose of this class
# is to identify the value by the class. # is to identify the value by the class.
class DRCAsDots class DRCOutputMode
attr_accessor :value attr_accessor :value
def initialize(v) def initialize(v)
self.value = v self.value = v

View File

@ -342,7 +342,7 @@ See <a href="/about/drc_ref_netter.xml#connect_global">Netter#connect_global</a>
<p> <p>
See <a href="/about/drc_ref_netter.xml#connect_implicit">Netter#connect_implicit</a> for a description of that function. See <a href="/about/drc_ref_netter.xml#connect_implicit">Netter#connect_implicit</a> for a description of that function.
</p> </p>
<a name="corners"/><h2>"corners" - Selects all polygons which are rectilinear</h2> <a name="corners"/><h2>"corners" - Selects corners of polygons</h2>
<keyword name="corners"/> <keyword name="corners"/>
<p>Usage:</p> <p>Usage:</p>
<ul> <ul>
@ -356,8 +356,9 @@ argument, "corners" represents the corner generator/filter in primary shapes for
<a href="/about/drc_ref_drc.xml">DRC</a> expressions (see <a href="/about/drc_ref_layer.xml#drc">Layer#drc</a> and <a href="/about/drc_ref_drc.xml#corners">DRC#corners</a> for more details). <a href="/about/drc_ref_drc.xml">DRC</a> expressions (see <a href="/about/drc_ref_layer.xml#drc">Layer#drc</a> and <a href="/about/drc_ref_drc.xml#corners">DRC#corners</a> for more details).
</p><p> </p><p>
Like the layer-based version, the "corners" operator accepts the Like the layer-based version, the "corners" operator accepts the
output type option: "as_dots" for dot-like edges and "as_boxes" for output type option: "as_dots" for dot-like edges, "as_boxes" for
small (2x2 DBU) box markers. small (2x2 DBU) box markers and "as_edge_pairs" for edge pairs.
The default output type is "as_boxes".
</p><p> </p><p>
The "corners" operator can be put into a condition which means it's The "corners" operator can be put into a condition which means it's
applied to corners meeting a particular angle constraint. applied to corners meeting a particular angle constraint.

View File

@ -263,6 +263,8 @@ The options available are:
<ul> <ul>
<li><b>as_boxes </b>: with this option, small boxes will be produced as markers </li> <li><b>as_boxes </b>: with this option, small boxes will be produced as markers </li>
<li><b>as_dots </b>: with this option, point-like edges will be produced instead of small boxes </li> <li><b>as_dots </b>: with this option, point-like edges will be produced instead of small boxes </li>
<li><b>as_edge_pairs </b>: with this option, an edge pair is produced for each corner selected. The first edge
is the incoming edge to the corner, the second edge the outgoing edge. </li>
</ul> </ul>
</p><p> </p><p>
The following images show the effect of this method: The following images show the effect of this method:

View File

@ -22,6 +22,7 @@ l1.drc(-90 < corners(as_dots) <= 90.0).output(101, 0)
l1.drc(corners(as_boxes) == -90).output(102, 0) # outer corners l1.drc(corners(as_boxes) == -90).output(102, 0) # outer corners
l1.drc(corners(as_boxes) == 90).output(103, 0) # inner corners l1.drc(corners(as_boxes) == 90).output(103, 0) # inner corners
l1.drc(corners(as_boxes) <= -90).output(104, 0) l1.drc(corners(as_boxes) <= -90).output(104, 0)
l1.drc(corners(as_edge_pairs) == 90).output(105, 0)
l1.drc(middle).output(110, 0) l1.drc(middle).output(110, 0)
l1.drc(middle(as_dots)).output(111, 0) l1.drc(middle(as_dots)).output(111, 0)

Binary file not shown.

Binary file not shown.

View File

@ -66,6 +66,9 @@ a1.extent_refs(0.25, 0.5, 0.5, 0.75).output(1053, 0)
a1.corners.sized(0.05).output(1060, 0) a1.corners.sized(0.05).output(1060, 0)
a1.corners(-90.0, as_boxes).sized(0.05).output(1061, 0) a1.corners(-90.0, as_boxes).sized(0.05).output(1061, 0)
a1.corners(-90.0, as_dots).extended(0.05, 0.05, 0.05, 0.05).output(1062, 0) a1.corners(-90.0, as_dots).extended(0.05, 0.05, 0.05, 0.05).output(1062, 0)
a1.corners(-90.0, as_edge_pairs).polygons(0).output(1063, 0)
a1.corners(-90.0, as_edge_pairs).first_edges.start_segments(0.1).extended(0.05, 0.05, 0.05, 0.05).output(1064, 0)
a1.corners(-90.0, as_edge_pairs).second_edges.start_segments(0.1).extended(0.05, 0.05, 0.05, 0.05).output(1065, 0)
a1.select { |p| p.bbox.width < 0.8 }.output(1100, 0) a1.select { |p| p.bbox.width < 0.8 }.output(1100, 0)
a1.collect { |p| p.is_box? && p.bbox.enlarged(0.1, 0.1) }.output(1101, 0) a1.collect { |p| p.is_box? && p.bbox.enlarged(0.1, 0.1) }.output(1101, 0)

Binary file not shown.