Some refactoring (angle check), angle check and length check for universal DRC, tests, bug fixes.

This commit is contained in:
Matthias Koefferlein 2021-01-10 19:54:16 +01:00
parent 158ea196ec
commit 0a7ca69da2
13 changed files with 180 additions and 88 deletions

View File

@ -207,4 +207,71 @@ EdgeSegmentSelector::process (const db::Edge &edge, std::vector<db::Edge> &res)
}
}
// -------------------------------------------------------------------------------------------------------------
// EdgeAngleChecker implementation
EdgeAngleChecker::EdgeAngleChecker (double angle_start, bool include_angle_start, double angle_end, bool include_angle_end)
{
m_t_start = db::CplxTrans(1.0, angle_start, false, db::DVector ());
m_t_end = db::CplxTrans(1.0, angle_end, false, db::DVector ());
m_include_start = include_angle_start;
m_include_end = include_angle_end;
m_big_angle = (angle_end - angle_start + db::epsilon) > 180.0;
m_all = (angle_end - angle_start - db::epsilon) > 360.0;
}
bool
EdgeAngleChecker::check (const db::Vector &a, const db::Vector &b) const
{
db::DVector vout (b);
db::DVector v1 = m_t_start * a;
db::DVector v2 = m_t_end * a;
int vps1 = db::vprod_sign (v1, vout);
int vps2 = db::vprod_sign (v2, vout);
bool opp1 = vps1 == 0 && (db::sprod_sign (v1, vout) < 0);
bool opp2 = vps2 == 0 && (db::sprod_sign (v2, vout) < 0);
bool vp1 = !opp1 && (m_include_start ? (db::vprod_sign (v1, vout) >= 0) : (db::vprod_sign (v1, vout) > 0));
bool vp2 = !opp2 && (m_include_end ? (db::vprod_sign (v2, vout) <= 0) : (db::vprod_sign (v2, vout) < 0));
if (m_big_angle && (vp1 || vp2)) {
return true;
} else if (! m_big_angle && vp1 && vp2) {
return true;
} else {
return false;
}
}
// -------------------------------------------------------------------------------------------------------------
// EdgeOrientationFilter implementation
EdgeOrientationFilter::EdgeOrientationFilter (double amin, bool include_amin, double amax, bool include_amax, bool inverse)
: m_inverse (inverse), m_checker (amin, include_amin, amax, include_amax)
{
// .. nothing yet ..
}
EdgeOrientationFilter::EdgeOrientationFilter (double a, bool inverse)
: m_inverse (inverse), m_checker (a, true, a, true)
{
// .. nothing yet ..
}
bool
EdgeOrientationFilter::selected (const db::Edge &edge) const
{
// NOTE: this edge normalization confines the angle to a range between (-90 .. 90] (-90 excluded).
// A horizontal edge has 0 degree, a vertical one has 90 degree.
if (edge.dx () < 0 || (edge.dx () == 0 && edge.dy () < 0)) {
return m_checker (db::Vector (edge.ortho_length (), 0), -edge.d ()) != m_inverse;
} else {
return m_checker (db::Vector (edge.ortho_length (), 0), edge.d ()) != m_inverse;
}
}
}

View File

@ -103,6 +103,41 @@ private:
db::MagnificationReducer m_vars;
};
/**
* @brief An angle detector
*
* This detector can check whether the angle between two edges is within a certain angle interval.
* It takes two edges: a and b. If b "turns left" (b following a), the angle will be positive, if it "turns" right,
* the angle will be negative. The angle can be between -180 and 180 degree. The case of reflection
* (exactly 180 degree) is not considered.
*
* The constraint can be given in terms of a minimum and maximum angle. "include" specifies whether the
* angle value itself is included. The operator() will return true, if the angle between the given
* edges a and b in matching the constraint.
*/
class DB_PUBLIC EdgeAngleChecker
{
public:
EdgeAngleChecker (double angle_start, bool include_angle_start, double angle_end, bool include_angle_end);
bool operator() (const db::Edge &a, const db::Edge &b) const
{
return m_all || check (a.d (), b.d ());
}
bool operator() (const db::Vector &a, const db::Vector &b) const
{
return m_all || check (a, b);
}
private:
db::CplxTrans m_t_start, m_t_end;
bool m_include_start, m_include_end;
bool m_big_angle, m_all;
bool check (const db::Vector &a, const db::Vector &b) const;
};
/**
* @brief An edge orientation filter for use with Edges::filter or Edges::filtered
*
@ -126,12 +161,7 @@ struct DB_PUBLIC EdgeOrientationFilter
* This filter will filter out all edges whose angle against x axis
* is larger or equal to amin and less than amax.
*/
EdgeOrientationFilter (double amin, double amax, bool inverse)
: m_inverse (inverse), m_exact (false)
{
m_emin = db::DVector (cos (amin * M_PI / 180.0), sin (amin * M_PI / 180.0));
m_emax = db::DVector (cos (amax * M_PI / 180.0), sin (amax * M_PI / 180.0));
}
EdgeOrientationFilter (double amin, bool include_amin, double amax, bool include_amax, bool inverse);
/**
* @brief Constructor
@ -142,33 +172,12 @@ struct DB_PUBLIC EdgeOrientationFilter
* This filter will filter out all edges whose angle against x axis
* is equal to a.
*/
EdgeOrientationFilter (double a, bool inverse)
: m_inverse (inverse), m_exact (true)
{
m_emin = db::DVector (cos (a * M_PI / 180.0), sin (a * M_PI / 180.0));
}
EdgeOrientationFilter (double a, bool inverse);
/**
* @brief Returns true if the edge orientation matches the criterion
*/
virtual bool selected (const db::Edge &edge) const
{
int smin = db::vprod_sign (m_emin, db::DVector (edge.d ()));
if (m_exact) {
if (! m_inverse) {
return smin == 0;
} else {
return smin != 0;
}
} else {
int smax = db::vprod_sign (m_emax, db::DVector (edge.d ()));
if (! m_inverse) {
return (smin >= 0 && smax < 0) || (smax > 0 && smin <= 0);
} else {
return ! ((smin >= 0 && smax < 0) || (smax > 0 && smin <= 0));
}
}
}
virtual bool selected (const db::Edge &edge) const;
/**
* @brief This filter is not isotropic
@ -197,8 +206,8 @@ struct DB_PUBLIC EdgeOrientationFilter
private:
db::DVector m_emin, m_emax;
bool m_inverse;
bool m_exact;
db::MagnificationAndOrientationReducer m_vars;
EdgeAngleChecker m_checker;
};
/**

View File

@ -31,15 +31,9 @@ namespace db
// CornerDetectorCore implementation
CornerDetectorCore::CornerDetectorCore (double angle_start, bool include_angle_start, double angle_end, bool include_angle_end)
: m_checker (angle_start, include_angle_start, angle_end, include_angle_end)
{
m_t_start = db::CplxTrans(1.0, angle_start, false, db::DVector ());
m_t_end = db::CplxTrans(1.0, angle_end, false, db::DVector ());
m_include_start = include_angle_start;
m_include_end = include_angle_end;
m_big_angle = (angle_end - angle_start + db::epsilon) > 180.0;
m_all = (angle_end - angle_start - db::epsilon) > 360.0;
// .. nothing yet ..
}
void CornerDetectorCore::detect_corners (const db::Polygon &poly, const CornerPointDelivery &delivery) const
@ -57,30 +51,8 @@ void CornerDetectorCore::detect_corners (const db::Polygon &poly, const CornerPo
db::Point pn = ctr [j];
if (m_all) {
if (m_checker (pt - pp, pn - pt)) {
delivery.make_point (pt);
} else {
db::Vector vin (pt - pp);
db::DVector vout (pn - pt);
db::DVector v1 = m_t_start * vin;
db::DVector v2 = m_t_end * vin;
int vps1 = db::vprod_sign (v1, vout);
int vps2 = db::vprod_sign (v2, vout);
bool opp1 = vps1 == 0 && (db::sprod_sign (v1, vout) < 0);
bool opp2 = vps2 == 0 && (db::sprod_sign (v2, vout) < 0);
bool vp1 = !opp1 && (m_include_start ? (db::vprod_sign (v1, vout) >= 0) : (db::vprod_sign (v1, vout) > 0));
bool vp2 = !opp2 && (m_include_end ? (db::vprod_sign (v2, vout) <= 0) : (db::vprod_sign (v2, vout) < 0));
if (m_big_angle && (vp1 || vp2)) {
delivery.make_point (pt);
} else if (! m_big_angle && vp1 && vp2) {
delivery.make_point (pt);
}
}
pp = pt;

View File

@ -27,6 +27,7 @@
#include "dbCommon.h"
#include "dbRegionDelegate.h"
#include "dbPolygonTools.h"
#include "dbEdgesUtils.h"
namespace db
{
@ -97,9 +98,7 @@ public:
void detect_corners (const db::Polygon &poly, const CornerPointDelivery &delivery) const;
private:
db::CplxTrans m_t_start, m_t_end;
bool m_include_start, m_include_end;
bool m_big_angle, m_all;
db::EdgeAngleChecker m_checker;
};
/**

View File

@ -326,10 +326,10 @@ static db::CompoundRegionOperationNode *new_edge_length_filter (db::CompoundRegi
return new db::CompoundRegionEdgeFilterOperationNode (new db::EdgeLengthFilter (lmin, lmax, inverse), input, true /*processor is owned*/);
}
static db::CompoundRegionOperationNode *new_edge_orientation_filter (db::CompoundRegionOperationNode *input, bool inverse, double amin, double amax)
static db::CompoundRegionOperationNode *new_edge_orientation_filter (db::CompoundRegionOperationNode *input, bool inverse, double amin, bool include_amin, double amax, bool include_amax)
{
check_non_null (input, "input");
return new db::CompoundRegionEdgeFilterOperationNode (new db::EdgeOrientationFilter (amin, amax, inverse), input, true /*processor is owned*/);
return new db::CompoundRegionEdgeFilterOperationNode (new db::EdgeOrientationFilter (amin, include_amin, amax, include_amax, inverse), input, true /*processor is owned*/);
}
static db::CompoundRegionOperationNode *new_polygons (db::CompoundRegionOperationNode *input, db::Coord e)
@ -675,7 +675,7 @@ Class<db::CompoundRegionOperationNode> decl_CompoundRegionOperationNode ("db", "
gsi::constructor ("new_edge_length_filter", &new_edge_length_filter, gsi::arg ("input"), gsi::arg ("inverse", false), gsi::arg ("lmin", 0), gsi::arg ("lmax", std::numeric_limits<db::Edge::distance_type>::max (), "max"),
"@brief Creates a node filtering edges by their length.\n"
) +
gsi::constructor ("new_edge_orientation_filter", &new_edge_orientation_filter, gsi::arg ("input"), gsi::arg ("inverse", false), gsi::arg ("amin"), gsi::arg ("amax"),
gsi::constructor ("new_edge_orientation_filter", &new_edge_orientation_filter, gsi::arg ("input"), gsi::arg ("inverse", false), gsi::arg ("amin"), gsi::arg ("include_amin"), gsi::arg ("amax"), gsi::arg ("include_amax"),
"@brief Creates a node filtering edges by their orientation.\n"
) +
gsi::constructor ("new_polygons", &new_polygons, gsi::arg ("input"), gsi::arg ("e", 0),

View File

@ -206,9 +206,9 @@ static db::Edges with_angle1 (const db::Edges *r, double a, bool inverse)
return r->filtered (f);
}
static db::Edges with_angle2 (const db::Edges *r, double amin, double amax, bool inverse)
static db::Edges with_angle2 (const db::Edges *r, double amin, double amax, bool inverse, bool include_amin, bool include_amax)
{
db::EdgeOrientationFilter f (amin, amax, inverse);
db::EdgeOrientationFilter f (amin, include_amin, amax, include_amax, inverse);
return r->filtered (f);
}
@ -581,12 +581,17 @@ Class<db::Edges> decl_Edges (decl_dbShapeCollection, "db", "Edges",
"horizontal = edges.with_orientation(0, true)\n"
"@/code\n"
) +
method_ext ("with_angle", with_angle2, gsi::arg ("min_angle"), gsi::arg ("max_angle"), gsi::arg ("inverse"),
method_ext ("with_angle", with_angle2, gsi::arg ("min_angle"), gsi::arg ("max_angle"), gsi::arg ("inverse"), gsi::arg ("include_amin", true), gsi::arg ("include_amax", true),
"@brief Filter the edges by orientation\n"
"Filters the edges in the edge collection by orientation. If \"inverse\" is false, only "
"edges which have an angle to the x-axis larger or equal to \"min_angle\" and less than \"max_angle\" are "
"returned. If \"inverse\" is true, "
"edges which do not conform to this criterion are returned."
"edges which do not conform to this criterion are returned.\n"
"\n"
"With \"include_min\" set to true (the default), the minimum angle is included in the criterion while with false, the "
"minimum angle itself is not included. Same for \"include_max\".\n"
"\n"
"The two \"include..\" arguments have been added in version 0.27."
) +
method ("insert", (void (db::Edges::*)(const db::Edge &)) &db::Edges::insert, gsi::arg ("edge"),
"@brief Inserts an edge\n"

View File

@ -259,8 +259,8 @@ TEST(5_Filters)
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (2, 0)), r2);
db::EdgeOrientationFilter eof1 (0, 1, false);
db::EdgeOrientationFilter eof2 (0, 1, true);
db::EdgeOrientationFilter eof1 (0, true, 1, true, false);
db::EdgeOrientationFilter eof2 (0, true, 1, true, true);
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (10, 0)), e2.filtered (eof1));
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (11, 0)), e2.filtered (eof2));

View File

@ -200,23 +200,23 @@ TEST(4)
EXPECT_EQ (r.filtered (f1).to_string (), "(0,200;100,200);(100,0;0,0);(300,0;200,0)");
}
{
db::EdgeOrientationFilter f1 (50.0, 80.0, false);
db::EdgeOrientationFilter f1 (50.0, true, 80.0, true, false);
EXPECT_EQ (r.filtered (f1).to_string (), "(200,0;250,200);(250,-200;300,0)");
}
{
db::EdgeOrientationFilter f1 (50.0, 80.0, true);
db::EdgeOrientationFilter f1 (50.0, true, 80.0, true, true);
EXPECT_EQ (r.filtered (f1).to_string (), "(0,0;0,200);(0,200;100,200);(100,200;100,0);(100,0;0,0);(250,200;300,0);(300,0;200,0);(200,0;250,-200)");
}
{
db::EdgeOrientationFilter f1 (0.0, 1.0, false);
db::EdgeOrientationFilter f1 (0.0, true, 1.0, true, false);
EXPECT_EQ (r.filtered (f1).to_string (), "(0,200;100,200);(100,0;0,0);(300,0;200,0)");
}
{
db::EdgeOrientationFilter f1 (-1.0, 1.0, false);
db::EdgeOrientationFilter f1 (-1.0, true, 1.0, true, false);
EXPECT_EQ (r.filtered (f1).to_string (), "(0,200;100,200);(100,0;0,0);(300,0;200,0)");
}
{
db::EdgeOrientationFilter f1 (-1.0, 0.0, false);
db::EdgeOrientationFilter f1 (-1.0, true, 0.0, true, false);
EXPECT_EQ (r.filtered (f1).to_string (), "");
}
{
@ -224,15 +224,15 @@ TEST(4)
EXPECT_EQ (r.filtered (f1).to_string (), "(0,0;0,200);(100,200;100,0)");
}
{
db::EdgeOrientationFilter f1 (90.0, 91.0, false);
db::EdgeOrientationFilter f1 (90.0, true, 91.0, true, false);
EXPECT_EQ (r.filtered (f1).to_string (), "(0,0;0,200);(100,200;100,0)");
}
{
db::EdgeOrientationFilter f1 (89.0, 91.0, false);
db::EdgeOrientationFilter f1 (89.0, true, 91.0, true, false);
EXPECT_EQ (r.filtered (f1).to_string (), "(0,0;0,200);(100,200;100,0)");
}
{
db::EdgeOrientationFilter f1 (89.0, 90.0, false);
db::EdgeOrientationFilter f1 (89.0, true, 90.0, true, false);
EXPECT_EQ (r.filtered (f1).to_string (), "");
}
}

View File

@ -1324,7 +1324,9 @@ class DRCOpNodeEdgeOrientationFilter < DRCOpNodeWithCompare
args = [ n, self.inverse ]
angle_delta = 1e-6
args << (self.gt ? self.gt + angle_delta : (self.ge ? self.ge : -180.0))
args << (self.gt ? false : true)
args << (self.lt ? self.lt : (self.le ? self.le + angle_delta : 180.0))
args << (self.lt ? false : true)
RBA::CompoundRegionOperationNode::new_edge_orientation_filter(*args)

View File

@ -334,13 +334,15 @@ CODE
# %DRC%
# @name area
# @brief Selects the primary shape if the area is meeting the condition
# @brief Computes the total area or in universal DRC context: selects the primary shape if the area is meeting the condition
# @synopsis area (in condition)
# @synopsis area(layer)
#
# This function can be used with a layer argument in which case it
# is equivalent to "layer.area" (see \Layer#area). Without a layer
# argument, "area" represents an area filter for primary shapes in
# is equivalent to "layer.area" (see \Layer#area) and returns the total area of the
# polygons in the layer.
#
# Without a layer argument, "area" represents an area filter for primary shapes in
# \DRC# expressions (see \Layer#drc and \DRC#area for more details).
# %DRC%
@ -378,13 +380,15 @@ CODE
# %DRC%
# @name perimeter
# @brief Selects the primary shape if the perimeter is meeting the condition
# @brief Computes the total perimeter or in universal DRC context: selects the primary shape if the perimeter is meeting the condition
# @synopsis perimeter (in condition)
# @synopsis perimeter(layer)
#
# This function can be used with a layer argument in which case it
# is equivalent to "layer.perimeter" (see \Layer#perimeter). Without a layer
# argument, "perimeter" represents a perimeter filter for primary shapes in
# is equivalent to "layer.perimeter" (see \Layer#perimeter) and returns the
# total perimeter of all polygons in the layer.
#
# Without a layer argument, "perimeter" represents a perimeter filter for primary shapes in
# \DRC# expressions (see \Layer#drc and \DRC#perimeter for more details).
# %DRC%
@ -409,6 +413,27 @@ CODE
# argument, "rectilinear" represents the rectilinear polygons filter for primary shapes in
# \DRC# expressions (see \Layer#drc and \DRC#rectilinear for more details).
# %DRC%
# @name length (in condition)
# @brief Computes the total edge length of an edge layer or in universal DRC context: selects edges based on a length condition
# @synopsis length (in condition)
# @synopsis length(layer)
#
# This function can be used with a layer argument in which case it
# is equivalent to "layer.length" (see \Layer#length). Without a layer
# argument, "length" represents the edge length filter on the primary shape edges in
# \DRC# expressions (see \Layer#drc and \DRC#length for more details). In this context,
# the operation acts similar to \Layer#with_length.
# %DRC%
# @name angle (in condition)
# @brief In universal DRC context: selects edges based on their orientation
# @synopsis angle (in condition)
#
# "angle" represents the edge orientation filter on the primary shape edges in
# \DRC# expressions (see \Layer#drc and \DRC#angle for more details). In this context,
# the operation acts similar to \Layer#with_angle.
%w(
area
holes
@ -417,6 +442,8 @@ CODE
perimeter
rectangles
rectilinear
length
angle
).each do |f|
# NOTE: these methods are fallback for the respective global ones which route to DRCLayer or here.
eval <<"CODE"

View File

@ -1574,6 +1574,7 @@ CODE
%w(
and
andnot
angle
area
bbox
centers

View File

@ -2780,7 +2780,7 @@ CODE
@engine._cmd(self.data, :length) * @engine.dbu.to_f
end
end
# %DRC%
# @name flatten
# @brief Flattens the layer

View File

@ -178,3 +178,13 @@ TEST(11d)
{
run_test (_this, "11", true);
}
TEST(12)
{
run_test (_this, "12", false);
}
TEST(12d)
{
run_test (_this, "12", true);
}