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];
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;

View File

@ -41,7 +41,7 @@ namespace db
class DB_PUBLIC CornerPointDelivery
{
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)
{ }
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)));
}
@ -76,7 +76,7 @@ public:
: 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));
}
@ -86,6 +86,26 @@ private:
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
*/
@ -155,6 +175,31 @@ public:
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

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*/);
}
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)
{
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"),
"@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),
"@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."

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));
}
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)
{
return new db::Region (si);
@ -1373,14 +1378,22 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"A similar function that reports corners as point-like edges is \\corners_dots.\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),
"@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 \\Edges collection with dot-like edges for each corner.\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,
"@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 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
if as_dots.is_a?(DRCAsDots)
as_dots = as_dots.value
if output_mode.is_a?(DRCOutputMode)
output_mode = output_mode.value
else
raise("Invalid argument (#{as_dots.inspect}) for 'corners' method")
end
DRCOpNodeCornersFilter::new(@engine, as_dots, self)
DRCOpNodeCornersFilter::new(@engine, output_mode, self)
end
end
@ -859,8 +859,8 @@ CODE
args.each_with_index do |a,ia|
if a.is_a?(1.0.class) && :#{f} != :middle
f << a
elsif a.is_a?(DRCAsDots)
as_edges = a.value
elsif a.is_a?(DRCOutputMode)
as_edges = (a.value == :edges || a.value == :dots)
elsif @@std_refs[a] && :#{f} != :middle
f = @@std_refs[a]
else
@ -1996,11 +1996,11 @@ end
class DRCOpNodeCornersFilter < DRCOpNodeWithCompare
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)
self.as_dots = as_dots
self.output_mode = output_mode
self.input = input
self.description = "corners"
end
@ -2011,8 +2011,10 @@ class DRCOpNodeCornersFilter < DRCOpNodeWithCompare
args << (self.gt ? false : true)
args << (self.lt ? self.lt : (self.le ? self.le : 180.0))
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)
elsif self.output_mode == :edge_pairs
RBA::CompoundRegionOperationNode::new_corners_as_edge_pairs(*args)
else
args << 2 # dimension is 2x2 DBU
RBA::CompoundRegionOperationNode::new_corners_as_rectangles(*args)

View File

@ -781,7 +781,7 @@ CODE
# %DRC%
# @name corners
# @brief Selects all polygons which are rectilinear
# @brief Selects corners of polygons
# @synopsis corners([ options ]) (in condition)
# @synopsis corners(layer [, options ])
#
@ -791,15 +791,16 @@ CODE
# \DRC# expressions (see \Layer#drc and \DRC#corners for more details).
#
# Like the layer-based version, the "corners" operator accepts the
# output type option: "as_dots" for dot-like edges and "as_boxes" for
# small (2x2 DBU) box markers.
# output type option: "as_dots" for dot-like edges, "as_boxes" for
# 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
# 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.
return primary.corners(as_dots)
return primary.corners(output_mode)
end
# %DRC%

View File

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

View File

@ -1118,8 +1118,8 @@ CODE
elsif a.is_a?(DRCPattern)
as_pattern = a.as_pattern
pattern = a.pattern
elsif a.is_a?(DRCAsDots)
as_dots = a.value
elsif a.is_a?(DRCOutputMode)
as_dots = (a.value == :edges || a.value == :dots)
else
raise("Invalid argument type #{a.inspect}")
end
@ -1167,6 +1167,8 @@ CODE
# @ul
# @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_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
#
# The following images show the effect of this method:
@ -1185,7 +1187,7 @@ CODE
requires_region
as_dots = false
output_mode = :boxes
amin = -180.0
amax = 180.0
@ -1199,14 +1201,23 @@ CODE
elsif a.is_a?(1.0.class) || a.is_a?(1.class)
amin = a.to_f
amax = a.to_f
elsif a.is_a?(DRCAsDots)
as_dots = a.value
elsif a.is_a?(DRCOutputMode)
output_mode = a.value
else
raise("Invalid argument #{a.inspect}")
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
@ -1375,8 +1386,8 @@ CODE
args.each do |a|
if a.is_a?(1.0.class) && :#{f} != :middle
f << a
elsif a.is_a?(DRCAsDots)
as_edges = a.value
elsif a.is_a?(DRCOutputMode)
as_edges = (a.value == :edges || a.value == :dots)
elsif @@std_refs[a] && :#{f} != :middle
f = @@std_refs[a]
else
@ -4736,7 +4747,7 @@ END
end
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
def requires_edges

View File

@ -77,7 +77,7 @@ module DRC
# A wrapper for the "as_dots" or "as_boxes" flag for
# some DRC functions. The purpose of this class
# is to identify the value by the class.
class DRCAsDots
class DRCOutputMode
attr_accessor :value
def initialize(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>
See <a href="/about/drc_ref_netter.xml#connect_implicit">Netter#connect_implicit</a> for a description of that function.
</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"/>
<p>Usage:</p>
<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).
</p><p>
Like the layer-based version, the "corners" operator accepts the
output type option: "as_dots" for dot-like edges and "as_boxes" for
small (2x2 DBU) box markers.
output type option: "as_dots" for dot-like edges, "as_boxes" for
small (2x2 DBU) box markers and "as_edge_pairs" for edge pairs.
The default output type is "as_boxes".
</p><p>
The "corners" operator can be put into a condition which means it's
applied to corners meeting a particular angle constraint.

View File

@ -263,6 +263,8 @@ The options available are:
<ul>
<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_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>
</p><p>
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(103, 0) # inner corners
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(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(-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_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.collect { |p| p.is_box? && p.bbox.enlarged(0.1, 0.1) }.output(1101, 0)

Binary file not shown.