mirror of https://github.com/KLayout/klayout.git
New region filters (square, area ratio, relative height)
This commit is contained in:
parent
ae29c75326
commit
dce22fee37
|
|
@ -437,6 +437,193 @@ poly2poly_check<PolygonType>::enter (const PolygonType &o1, size_t p1, const Pol
|
|||
template class poly2poly_check<db::Polygon>;
|
||||
template class poly2poly_check<db::PolygonRef>;
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// RegionPerimeterFilter implementation
|
||||
|
||||
RegionPerimeterFilter::RegionPerimeterFilter (perimeter_type pmin, perimeter_type pmax, bool inverse)
|
||||
: m_pmin (pmin), m_pmax (pmax), m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
bool RegionPerimeterFilter::selected (const db::Polygon &poly) const
|
||||
{
|
||||
perimeter_type p = 0;
|
||||
for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end () && p < m_pmax; ++e) {
|
||||
p += (*e).length ();
|
||||
}
|
||||
|
||||
if (! m_inverse) {
|
||||
return p >= m_pmin && p < m_pmax;
|
||||
} else {
|
||||
return ! (p >= m_pmin && p < m_pmax);
|
||||
}
|
||||
}
|
||||
|
||||
const TransformationReducer *RegionPerimeterFilter::vars () const
|
||||
{
|
||||
return &m_vars;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// RegionAreaFilter implementation
|
||||
|
||||
RegionAreaFilter::RegionAreaFilter (area_type amin, area_type amax, bool inverse)
|
||||
: m_amin (amin), m_amax (amax), m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
bool
|
||||
RegionAreaFilter::selected (const db::Polygon &poly) const
|
||||
{
|
||||
area_type a = poly.area ();
|
||||
if (! m_inverse) {
|
||||
return a >= m_amin && a < m_amax;
|
||||
} else {
|
||||
return ! (a >= m_amin && a < m_amax);
|
||||
}
|
||||
}
|
||||
|
||||
const TransformationReducer *
|
||||
RegionAreaFilter::vars () const
|
||||
{
|
||||
return &m_vars;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// RectilinearFilter implementation
|
||||
|
||||
RectilinearFilter::RectilinearFilter (bool inverse)
|
||||
: m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
bool
|
||||
RectilinearFilter::selected (const db::Polygon &poly) const
|
||||
{
|
||||
return poly.is_rectilinear () != m_inverse;
|
||||
}
|
||||
|
||||
const TransformationReducer *
|
||||
RectilinearFilter::vars () const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// RectilinearFilter implementation
|
||||
|
||||
RectangleFilter::RectangleFilter (bool is_square, bool inverse)
|
||||
: m_is_square (is_square), m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
bool
|
||||
RectangleFilter::selected (const db::Polygon &poly) const
|
||||
{
|
||||
bool ok = poly.is_box ();
|
||||
if (ok && m_is_square) {
|
||||
db::Box box = poly.box ();
|
||||
ok = box.width () == box.height ();
|
||||
}
|
||||
return ok != m_inverse;
|
||||
}
|
||||
|
||||
const TransformationReducer *RectangleFilter::vars () const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// RectilinearFilter implementation
|
||||
|
||||
RegionBBoxFilter::RegionBBoxFilter (value_type vmin, value_type vmax, bool inverse, parameter_type parameter)
|
||||
: m_vmin (vmin), m_vmax (vmax), m_inverse (inverse), m_parameter (parameter)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
bool
|
||||
RegionBBoxFilter::selected (const db::Polygon &poly) const
|
||||
{
|
||||
value_type v = 0;
|
||||
db::Box box = poly.box ();
|
||||
if (m_parameter == BoxWidth) {
|
||||
v = box.width ();
|
||||
} else if (m_parameter == BoxHeight) {
|
||||
v = box.height ();
|
||||
} else if (m_parameter == BoxMinDim) {
|
||||
v = std::min (box.width (), box.height ());
|
||||
} else if (m_parameter == BoxMaxDim) {
|
||||
v = std::max (box.width (), box.height ());
|
||||
} else if (m_parameter == BoxAverageDim) {
|
||||
v = (box.width () + box.height ()) / 2;
|
||||
}
|
||||
if (! m_inverse) {
|
||||
return v >= m_vmin && v < m_vmax;
|
||||
} else {
|
||||
return ! (v >= m_vmin && v < m_vmax);
|
||||
}
|
||||
}
|
||||
|
||||
const TransformationReducer *
|
||||
RegionBBoxFilter::vars () const
|
||||
{
|
||||
if (m_parameter != BoxWidth && m_parameter != BoxHeight) {
|
||||
return &m_isotropic_vars;
|
||||
} else {
|
||||
return &m_anisotropic_vars;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// RectilinearFilter implementation
|
||||
|
||||
RegionRatioFilter::RegionRatioFilter (double vmin, bool min_included, double vmax, bool max_included, bool inverse, parameter_type parameter)
|
||||
: m_vmin (vmin), m_vmax (vmax), m_vmin_included (min_included), m_vmax_included (max_included), m_inverse (inverse), m_parameter (parameter)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
bool RegionRatioFilter::selected (const db::Polygon &poly) const
|
||||
{
|
||||
double v = 0.0;
|
||||
if (m_parameter == AreaRatio) {
|
||||
v = poly.area_ratio ();
|
||||
} else if (m_parameter == AspectRatio) {
|
||||
db::Box box = poly.box ();
|
||||
double f = std::max (box.height (), box.width ());
|
||||
double d = std::min (box.height (), box.width ());
|
||||
if (d < 1) {
|
||||
return false;
|
||||
}
|
||||
v = f / d;
|
||||
} else if (m_parameter == RelativeHeight) {
|
||||
db::Box box = poly.box ();
|
||||
double f = box.height ();
|
||||
double d = box.width ();
|
||||
if (d < 1) {
|
||||
return false;
|
||||
}
|
||||
v = f / d;
|
||||
}
|
||||
|
||||
bool ok = (v - (m_vmin_included ? -db::epsilon : db::epsilon) > m_vmin && v - (m_vmax_included ? db::epsilon : -db::epsilon) < m_vmax);
|
||||
return ok != m_inverse;
|
||||
}
|
||||
|
||||
const TransformationReducer *RegionRatioFilter::vars () const
|
||||
{
|
||||
if (m_parameter != RelativeHeight) {
|
||||
return &m_isotropic_vars;
|
||||
} else {
|
||||
return &m_anisotropic_vars;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// SinglePolygonCheck implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -52,36 +52,17 @@ struct DB_PUBLIC RegionPerimeterFilter
|
|||
* @param amax The maximum perimeter (only polygons with a perimeter below this value are filtered)
|
||||
* @param inverse If set to true, only polygons not matching this criterion will be filtered
|
||||
*/
|
||||
RegionPerimeterFilter (perimeter_type pmin, perimeter_type pmax, bool inverse)
|
||||
: m_pmin (pmin), m_pmax (pmax), m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
RegionPerimeterFilter (perimeter_type pmin, perimeter_type pmax, bool inverse);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the polygon's perimeter matches the criterion
|
||||
*/
|
||||
virtual bool selected (const db::Polygon &poly) const
|
||||
{
|
||||
perimeter_type p = 0;
|
||||
for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end () && p < m_pmax; ++e) {
|
||||
p += (*e).length ();
|
||||
}
|
||||
|
||||
if (! m_inverse) {
|
||||
return p >= m_pmin && p < m_pmax;
|
||||
} else {
|
||||
return ! (p >= m_pmin && p < m_pmax);
|
||||
}
|
||||
}
|
||||
virtual bool selected (const db::Polygon &poly) const;
|
||||
|
||||
/**
|
||||
* @brief This filter is isotropic
|
||||
*/
|
||||
virtual const TransformationReducer *vars () const
|
||||
{
|
||||
return &m_vars;
|
||||
}
|
||||
virtual const TransformationReducer *vars () const;
|
||||
|
||||
/**
|
||||
* @brief This filter prefers producing variants
|
||||
|
|
@ -120,32 +101,17 @@ struct DB_PUBLIC RegionAreaFilter
|
|||
* @param amax The maximum area (only polygons with an area below this value are filtered)
|
||||
* @param inverse If set to true, only polygons not matching this criterion will be filtered
|
||||
*/
|
||||
RegionAreaFilter (area_type amin, area_type amax, bool inverse)
|
||||
: m_amin (amin), m_amax (amax), m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
RegionAreaFilter (area_type amin, area_type amax, bool inverse);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the polygon's area matches the criterion
|
||||
*/
|
||||
virtual bool selected (const db::Polygon &poly) const
|
||||
{
|
||||
area_type a = poly.area ();
|
||||
if (! m_inverse) {
|
||||
return a >= m_amin && a < m_amax;
|
||||
} else {
|
||||
return ! (a >= m_amin && a < m_amax);
|
||||
}
|
||||
}
|
||||
virtual bool selected (const db::Polygon &poly) const;
|
||||
|
||||
/**
|
||||
* @brief This filter is isotropic
|
||||
*/
|
||||
virtual const TransformationReducer *vars () const
|
||||
{
|
||||
return &m_vars;
|
||||
}
|
||||
virtual const TransformationReducer *vars () const;
|
||||
|
||||
/**
|
||||
* @brief This filter prefers producing variants
|
||||
|
|
@ -176,27 +142,17 @@ struct DB_PUBLIC RectilinearFilter
|
|||
* @brief Constructor
|
||||
* @param inverse If set to true, only polygons not matching this criterion will be filtered
|
||||
*/
|
||||
RectilinearFilter (bool inverse)
|
||||
: m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
RectilinearFilter (bool inverse);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the polygon's area matches the criterion
|
||||
*/
|
||||
virtual bool selected (const db::Polygon &poly) const
|
||||
{
|
||||
return poly.is_rectilinear () != m_inverse;
|
||||
}
|
||||
virtual bool selected (const db::Polygon &poly) const;
|
||||
|
||||
/**
|
||||
* @brief This filter does not need variants
|
||||
*/
|
||||
virtual const TransformationReducer *vars () const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
virtual const TransformationReducer *vars () const;
|
||||
|
||||
/**
|
||||
* @brief This filter prefers producing variants
|
||||
|
|
@ -225,27 +181,17 @@ struct DB_PUBLIC RectangleFilter
|
|||
* @brief Constructor
|
||||
* @param inverse If set to true, only polygons not matching this criterion will be filtered
|
||||
*/
|
||||
RectangleFilter (bool inverse)
|
||||
: m_inverse (inverse)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
RectangleFilter (bool is_square, bool inverse);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the polygon's area matches the criterion
|
||||
*/
|
||||
virtual bool selected (const db::Polygon &poly) const
|
||||
{
|
||||
return poly.is_box () != m_inverse;
|
||||
}
|
||||
virtual bool selected (const db::Polygon &poly) const;
|
||||
|
||||
/**
|
||||
* @brief This filter does not need variants
|
||||
*/
|
||||
virtual const TransformationReducer *vars () const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
virtual const TransformationReducer *vars () const;
|
||||
|
||||
/**
|
||||
* @brief This filter prefers producing variants
|
||||
|
|
@ -258,6 +204,7 @@ struct DB_PUBLIC RectangleFilter
|
|||
virtual bool requires_raw_input () const { return false; }
|
||||
|
||||
private:
|
||||
bool m_is_square;
|
||||
bool m_inverse;
|
||||
};
|
||||
|
||||
|
|
@ -300,48 +247,17 @@ struct DB_PUBLIC RegionBBoxFilter
|
|||
* @param vmax The max value (only polygons with bounding box parameters below this value are filtered)
|
||||
* @param inverse If set to true, only polygons not matching this criterion will be filtered
|
||||
*/
|
||||
RegionBBoxFilter (value_type vmin, value_type vmax, bool inverse, parameter_type parameter)
|
||||
: m_vmin (vmin), m_vmax (vmax), m_inverse (inverse), m_parameter (parameter)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
RegionBBoxFilter (value_type vmin, value_type vmax, bool inverse, parameter_type parameter);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the polygon's area matches the criterion
|
||||
*/
|
||||
virtual bool selected (const db::Polygon &poly) const
|
||||
{
|
||||
value_type v = 0;
|
||||
db::Box box = poly.box ();
|
||||
if (m_parameter == BoxWidth) {
|
||||
v = box.width ();
|
||||
} else if (m_parameter == BoxHeight) {
|
||||
v = box.height ();
|
||||
} else if (m_parameter == BoxMinDim) {
|
||||
v = std::min (box.width (), box.height ());
|
||||
} else if (m_parameter == BoxMaxDim) {
|
||||
v = std::max (box.width (), box.height ());
|
||||
} else if (m_parameter == BoxAverageDim) {
|
||||
v = (box.width () + box.height ()) / 2;
|
||||
}
|
||||
if (! m_inverse) {
|
||||
return v >= m_vmin && v < m_vmax;
|
||||
} else {
|
||||
return ! (v >= m_vmin && v < m_vmax);
|
||||
}
|
||||
}
|
||||
virtual bool selected (const db::Polygon &poly) const;
|
||||
|
||||
/**
|
||||
* @brief This filter is isotropic unless the parameter is width or height
|
||||
*/
|
||||
virtual const TransformationReducer *vars () const
|
||||
{
|
||||
if (m_parameter != BoxWidth && m_parameter != BoxHeight) {
|
||||
return &m_isotropic_vars;
|
||||
} else {
|
||||
return &m_anisotropic_vars;
|
||||
}
|
||||
}
|
||||
virtual const TransformationReducer *vars () const;
|
||||
|
||||
/**
|
||||
* @brief This filter prefers producing variants
|
||||
|
|
@ -361,6 +277,69 @@ private:
|
|||
db::MagnificationAndOrientationReducer m_anisotropic_vars;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A ratio filter for use with Region::filter or Region::filtered
|
||||
*
|
||||
* This filter can select polygons based on certain ratio values.
|
||||
* "ratio values" are typically in the order of 1 and floating point
|
||||
* values. Ratio values are always >= 0.
|
||||
*
|
||||
* Available ratio values are:
|
||||
* - (AreaRatio) area ratio (bounding box area vs. polygon area)
|
||||
* - (AspectRatio) bounding box aspect ratio (max / min)
|
||||
* - (RelativeHeight) bounding box height to width (tallness)
|
||||
*/
|
||||
|
||||
struct DB_PUBLIC RegionRatioFilter
|
||||
: public PolygonFilterBase
|
||||
{
|
||||
/**
|
||||
* @brief The parameters available
|
||||
*/
|
||||
enum parameter_type {
|
||||
AreaRatio,
|
||||
AspectRatio,
|
||||
RelativeHeight
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
*
|
||||
* @param vmin The min value (only polygons with bounding box parameters above this value are filtered)
|
||||
* @param vmax The max value (only polygons with bounding box parameters below this value are filtered)
|
||||
* @param inverse If set to true, only polygons not matching this criterion will be filtered
|
||||
*/
|
||||
RegionRatioFilter (double vmin, bool min_included, double vmax, bool max_included, bool inverse, parameter_type parameter);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the polygon's area matches the criterion
|
||||
*/
|
||||
virtual bool selected (const db::Polygon &poly) const;
|
||||
|
||||
/**
|
||||
* @brief This filter is isotropic unless the parameter is width or height
|
||||
*/
|
||||
virtual const TransformationReducer *vars () const;
|
||||
|
||||
/**
|
||||
* @brief This filter prefers producing variants
|
||||
*/
|
||||
virtual bool wants_variants () const { return true; }
|
||||
|
||||
/**
|
||||
* @brief This filter wants merged input
|
||||
*/
|
||||
virtual bool requires_raw_input () const { return false; }
|
||||
|
||||
private:
|
||||
double m_vmin, m_vmax;
|
||||
bool m_vmin_included, m_vmax_included;
|
||||
bool m_inverse;
|
||||
parameter_type m_parameter;
|
||||
db::MagnificationReducer m_isotropic_vars;
|
||||
db::MagnificationAndOrientationReducer m_anisotropic_vars;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A polygon processor filtering strange polygons
|
||||
*
|
||||
|
|
|
|||
|
|
@ -484,10 +484,10 @@ static db::CompoundRegionOperationNode *new_rectilinear_filter (db::CompoundRegi
|
|||
return new db::CompoundRegionFilterOperationNode (new db::RectilinearFilter (inverse), input, true);
|
||||
}
|
||||
|
||||
static db::CompoundRegionOperationNode *new_rectangle_filter (db::CompoundRegionOperationNode *input, bool inverse)
|
||||
static db::CompoundRegionOperationNode *new_rectangle_filter (db::CompoundRegionOperationNode *input, bool square, bool inverse)
|
||||
{
|
||||
check_non_null (input, "input");
|
||||
return new db::CompoundRegionFilterOperationNode (new db::RectangleFilter (inverse), input, true);
|
||||
return new db::CompoundRegionFilterOperationNode (new db::RectangleFilter (square, inverse), input, true);
|
||||
}
|
||||
|
||||
static db::CompoundRegionOperationNode *new_bbox_filter (db::CompoundRegionOperationNode *input, db::RegionBBoxFilter::parameter_type parameter, bool inverse, db::coord_traits<db::Coord>::distance_type vmin, db::coord_traits<db::Coord>::distance_type vmax)
|
||||
|
|
@ -666,8 +666,9 @@ Class<db::CompoundRegionOperationNode> decl_CompoundRegionOperationNode ("db", "
|
|||
gsi::constructor ("new_rectilinear_filter", &new_rectilinear_filter, gsi::arg ("input"), gsi::arg ("inverse", false),
|
||||
"@brief Creates a node filtering the input for rectilinear shapes (or non-rectilinear ones with 'inverse' set to 'true').\n"
|
||||
) +
|
||||
gsi::constructor ("new_rectangle_filter", &new_rectangle_filter, gsi::arg ("input"), gsi::arg ("inverse", false),
|
||||
"@brief Creates a node filtering the input for rectangular shapes (or non-rectangular ones with 'inverse' set to 'true').\n"
|
||||
gsi::constructor ("new_rectangle_filter", &new_rectangle_filter, gsi::arg ("input"), gsi::arg ("is_square", false), gsi::arg ("inverse", false),
|
||||
"@brief Creates a node filtering the input for rectangular or square shapes.\n"
|
||||
"If 'is_square' is true, only squares will be selected. If 'inverse' is true, the non-rectangle/non-square shapes are returned.\n"
|
||||
) +
|
||||
gsi::constructor ("new_edges", &new_edges, gsi::arg ("input"),
|
||||
"@brief Creates a node converting polygons into it's edges.\n"
|
||||
|
|
|
|||
|
|
@ -366,6 +366,42 @@ static db::EdgePairs angle_check2 (const db::Region *r, double amin, double amax
|
|||
return r->angle_check (amin, amax, inverse);
|
||||
}
|
||||
|
||||
static db::Region with_bbox_aspect_ratio1 (const db::Region *r, double v, bool inverse)
|
||||
{
|
||||
db::RegionRatioFilter f (v, true, v, true, inverse, db::RegionRatioFilter::AspectRatio);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region with_bbox_aspect_ratio2 (const db::Region *r, const tl::Variant &min, const tl::Variant &max, bool inverse, bool min_included, bool max_included)
|
||||
{
|
||||
db::RegionRatioFilter f (min.is_nil () ? 0.0 : min.to<double> (), min_included, max.is_nil () ? std::numeric_limits <double>::max () : max.to<double> (), max_included, inverse, db::RegionRatioFilter::AspectRatio);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region with_area_ratio1 (const db::Region *r, double v, bool inverse)
|
||||
{
|
||||
db::RegionRatioFilter f (v, true, v, true, inverse, db::RegionRatioFilter::AreaRatio);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region with_area_ratio2 (const db::Region *r, const tl::Variant &min, const tl::Variant &max, bool inverse, bool min_included, bool max_included)
|
||||
{
|
||||
db::RegionRatioFilter f (min.is_nil () ? 0.0 : min.to<double> (), min_included, max.is_nil () ? std::numeric_limits <double>::max () : max.to<double> (), max_included, inverse, db::RegionRatioFilter::AreaRatio);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region with_relative_height1 (const db::Region *r, double v, bool inverse)
|
||||
{
|
||||
db::RegionRatioFilter f (v, true, v, true, inverse, db::RegionRatioFilter::RelativeHeight);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region with_relative_height2 (const db::Region *r, const tl::Variant &min, const tl::Variant &max, bool inverse, bool min_included, bool max_included)
|
||||
{
|
||||
db::RegionRatioFilter f (min.is_nil () ? 0.0 : min.to<double> (), min_included, max.is_nil () ? std::numeric_limits <double>::max () : max.to<double> (), max_included, inverse, db::RegionRatioFilter::RelativeHeight);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region in (const db::Region *r, const db::Region &other)
|
||||
{
|
||||
return r->in (other, false);
|
||||
|
|
@ -378,13 +414,25 @@ static db::Region not_in (const db::Region *r, const db::Region &other)
|
|||
|
||||
static db::Region rectangles (const db::Region *r)
|
||||
{
|
||||
db::RectangleFilter f (false);
|
||||
db::RectangleFilter f (false, false);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region non_rectangles (const db::Region *r)
|
||||
{
|
||||
db::RectangleFilter f (true);
|
||||
db::RectangleFilter f (false, true);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region squares (const db::Region *r)
|
||||
{
|
||||
db::RectangleFilter f (true, false);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
static db::Region non_squares (const db::Region *r)
|
||||
{
|
||||
db::RectangleFilter f (true, true);
|
||||
return r->filtered (f);
|
||||
}
|
||||
|
||||
|
|
@ -839,7 +887,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_perimeter", with_perimeter1, gsi::arg ("perimeter"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by perimeter\n"
|
||||
"Filters the polygons inside the region by perimeter. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by perimeter. If \"inverse\" is false, only "
|
||||
"polygons which have the given perimeter are returned. If \"inverse\" is true, "
|
||||
"polygons not having the given perimeter are returned.\n"
|
||||
"\n"
|
||||
|
|
@ -847,7 +895,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_perimeter", with_perimeter2, gsi::arg ("min_perimeter"), gsi::arg ("max_perimeter"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by perimeter\n"
|
||||
"Filters the polygons inside the region by perimeter. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by perimeter. If \"inverse\" is false, only "
|
||||
"polygons which have a perimeter larger or equal to \"min_perimeter\" and less than \"max_perimeter\" are "
|
||||
"returned. If \"inverse\" is true, "
|
||||
"polygons having a perimeter less than \"min_perimeter\" or larger or equal than \"max_perimeter\" are "
|
||||
|
|
@ -859,7 +907,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_area", with_area1, gsi::arg ("area"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by area\n"
|
||||
"Filters the polygons inside the region by area. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by area. If \"inverse\" is false, only "
|
||||
"polygons which have the given area are returned. If \"inverse\" is true, "
|
||||
"polygons not having the given area are returned.\n"
|
||||
"\n"
|
||||
|
|
@ -867,7 +915,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_area", with_area2, gsi::arg ("min_area"), gsi::arg ("max_area"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by area\n"
|
||||
"Filters the polygons inside the region by area. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by area. If \"inverse\" is false, only "
|
||||
"polygons which have an area larger or equal to \"min_area\" and less than \"max_area\" are "
|
||||
"returned. If \"inverse\" is true, "
|
||||
"polygons having an area less than \"min_area\" or larger or equal than \"max_area\" are "
|
||||
|
|
@ -879,7 +927,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_bbox_width", with_bbox_width1, gsi::arg ("width"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by bounding box width\n"
|
||||
"Filters the polygons inside the region by the width of their bounding box. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by the width of their bounding box. If \"inverse\" is false, only "
|
||||
"polygons whose bounding box has the given width are returned. If \"inverse\" is true, "
|
||||
"polygons whose bounding box does not have the given width are returned.\n"
|
||||
"\n"
|
||||
|
|
@ -887,7 +935,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_bbox_width", with_bbox_width2, gsi::arg ("min_width"), gsi::arg ("max_width"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by bounding box width\n"
|
||||
"Filters the polygons inside the region by the width of their bounding box. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by the width of their bounding box. If \"inverse\" is false, only "
|
||||
"polygons whose bounding box has a width larger or equal to \"min_width\" and less than \"max_width\" are "
|
||||
"returned. If \"inverse\" is true, all polygons not matching this criterion are returned."
|
||||
"\n"
|
||||
|
|
@ -897,7 +945,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_bbox_height", with_bbox_height1, gsi::arg ("height"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by bounding box height\n"
|
||||
"Filters the polygons inside the region by the height of their bounding box. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by the height of their bounding box. If \"inverse\" is false, only "
|
||||
"polygons whose bounding box has the given height are returned. If \"inverse\" is true, "
|
||||
"polygons whose bounding box does not have the given height are returned.\n"
|
||||
"\n"
|
||||
|
|
@ -905,7 +953,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_bbox_height", with_bbox_height2, gsi::arg ("min_height"), gsi::arg ("max_height"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by bounding box height\n"
|
||||
"Filters the polygons inside the region by the height of their bounding box. If \"inverse\" is false, only "
|
||||
"Filters the polygons of the region by the height of their bounding box. If \"inverse\" is false, only "
|
||||
"polygons whose bounding box has a height larger or equal to \"min_height\" and less than \"max_height\" are "
|
||||
"returned. If \"inverse\" is true, all polygons not matching this criterion are returned."
|
||||
"\n"
|
||||
|
|
@ -924,7 +972,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_bbox_min", with_bbox_min2, gsi::arg ("min_dim"), gsi::arg ("max_dim"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by bounding box width or height, whichever is smaller\n"
|
||||
"Filters the polygons inside the region by the minimum dimension of their bounding box. "
|
||||
"Filters the polygons of the region by the minimum dimension of their bounding box. "
|
||||
"If \"inverse\" is false, only polygons whose bounding box's smaller dimension is larger or equal to \"min_dim\" "
|
||||
"and less than \"max_dim\" are returned. "
|
||||
"If \"inverse\" is true, all polygons not matching this criterion are returned."
|
||||
|
|
@ -935,7 +983,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_bbox_max", with_bbox_max1, gsi::arg ("dim"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by bounding box width or height, whichever is larger\n"
|
||||
"Filters the polygons inside the region by the maximum dimension of their bounding box. "
|
||||
"Filters the polygons of the region by the maximum dimension of their bounding box. "
|
||||
"If \"inverse\" is false, only polygons whose bounding box's larger dimension is equal to the given value "
|
||||
"are returned. "
|
||||
"If \"inverse\" is true, all polygons not matching this criterion are returned."
|
||||
|
|
@ -944,7 +992,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
) +
|
||||
method_ext ("with_bbox_max", with_bbox_max2, gsi::arg ("min_dim"), gsi::arg ("max_dim"), gsi::arg ("inverse"),
|
||||
"@brief Filter the polygons by bounding box width or height, whichever is larger\n"
|
||||
"Filters the polygons inside the region by the minimum dimension of their bounding box. "
|
||||
"Filters the polygons of the region by the minimum dimension of their bounding box. "
|
||||
"If \"inverse\" is false, only polygons whose bounding box's larger dimension is larger or equal to \"min_dim\" "
|
||||
"and less than \"max_dim\" are returned. "
|
||||
"If \"inverse\" is true, all polygons not matching this criterion are returned."
|
||||
|
|
@ -953,6 +1001,95 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
"\n"
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
) +
|
||||
method_ext ("with_bbox_aspect_ratio", with_bbox_aspect_ratio1, gsi::arg ("ratio"), gsi::arg ("inverse"),
|
||||
"@brief Filters the polygons by the aspect ratio of their bounding boxes\n"
|
||||
"Filters the polygons of the region by the apspect ratio of their bounding boxes. "
|
||||
"The aspect ratio is the ratio of larger to smaller dimension of the bounding box. "
|
||||
"A square has an aspect ratio of 1.\n"
|
||||
"\n"
|
||||
"With 'inverse' set to false, this version filters polygons which have a bounding box aspect ratio equal to the given value. "
|
||||
"With 'inverse' set to true, all other polygons will be returned.\n"
|
||||
"\n"
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method_ext ("with_bbox_aspect_ratio", with_bbox_aspect_ratio2, gsi::arg ("min_ratio"), gsi::arg ("max_ratio"), gsi::arg ("inverse"), gsi::arg ("min_included", true), gsi::arg ("max_included", true),
|
||||
"@brief Filters the polygons by the aspect ratio of their bounding boxes\n"
|
||||
"Filters the polygons of the region by the apspect ratio of their bounding boxes. "
|
||||
"The aspect ratio is the ratio of larger to smaller dimension of the bounding box. "
|
||||
"A square has an aspect ratio of 1.\n"
|
||||
"\n"
|
||||
"With 'inverse' set to false, this version filters polygons which have a bounding box aspect ratio between 'min_ratio' and 'max_ratio'. "
|
||||
"With 'min_included' set to true, the 'min_ratio' value is included in the range, otherwise it's excluded. Same for 'max_included' and 'max_ratio'. "
|
||||
"With 'inverse' set to true, all other polygons will be returned.\n"
|
||||
"\n"
|
||||
"If you don't want to specify a lower or upper limit, pass nil to that parameter.\n"
|
||||
"\n"
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method_ext ("with_area_ratio", with_area_ratio1, gsi::arg ("ratio"), gsi::arg ("inverse"),
|
||||
"@brief Filters the polygons by the bounding box area to polygon area ratio\n"
|
||||
"The area ratio is defined by the ratio of bounding box area to polygon area. It's a measure "
|
||||
"how much the bounding box is approximating the polygon. 'Thin polygons' have a large area ratio, boxes has an area ratio of 1.\n"
|
||||
"The area ratio is always larger or equal to 1. "
|
||||
"\n"
|
||||
"With 'inverse' set to false, this version filters polygons which have an area ratio equal to the given value. "
|
||||
"With 'inverse' set to true, all other polygons will be returned.\n"
|
||||
"\n"
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method_ext ("with_area_ratio", with_area_ratio2, gsi::arg ("min_ratio"), gsi::arg ("max_ratio"), gsi::arg ("inverse"), gsi::arg ("min_included", true), gsi::arg ("max_included", true),
|
||||
"@brief Filters the polygons by the aspect ratio of their bounding boxes\n"
|
||||
"The area ratio is defined by the ratio of bounding box area to polygon area. It's a measure "
|
||||
"how much the bounding box is approximating the polygon. 'Thin polygons' have a large area ratio, boxes has an area ratio of 1.\n"
|
||||
"The area ratio is always larger or equal to 1. "
|
||||
"\n"
|
||||
"With 'inverse' set to false, this version filters polygons which have an area ratio between 'min_ratio' and 'max_ratio'. "
|
||||
"With 'min_included' set to true, the 'min_ratio' value is included in the range, otherwise it's excluded. Same for 'max_included' and 'max_ratio'. "
|
||||
"With 'inverse' set to true, all other polygons will be returned.\n"
|
||||
"\n"
|
||||
"If you don't want to specify a lower or upper limit, pass nil to that parameter.\n"
|
||||
"\n"
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method_ext ("with_relative_height", with_relative_height1, gsi::arg ("ratio"), gsi::arg ("inverse"),
|
||||
"@brief Filters the polygons by the ratio of heigth to width\n"
|
||||
"This method filters the polygons of the region by the ratio of height vs. width of their bounding boxes. "
|
||||
"'Tall' polygons have a large value while 'flat' polygons have a small value. A square has a relative height of 1.\n"
|
||||
"\n"
|
||||
"An alternative method is 'with_area_ratio' which can be more efficient because it's isotropic.\n"
|
||||
"\n"
|
||||
"With 'inverse' set to false, this version filters polygons which have a relative height equal to the given value. "
|
||||
"With 'inverse' set to true, all other polygons will be returned.\n"
|
||||
"\n"
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method_ext ("with_relative_height", with_relative_height2, gsi::arg ("min_ratio"), gsi::arg ("max_ratio"), gsi::arg ("inverse"), gsi::arg ("min_included", true), gsi::arg ("max_included", true),
|
||||
"@brief Filters the polygons by the bounding box height to width ratio\n"
|
||||
"This method filters the polygons of the region by the ratio of height vs. width of their bounding boxes. "
|
||||
"'Tall' polygons have a large value while 'flat' polygons have a small value. A square has a relative height of 1.\n"
|
||||
"\n"
|
||||
"An alternative method is 'with_area_ratio' which can be more efficient because it's isotropic.\n"
|
||||
"\n"
|
||||
"With 'inverse' set to false, this version filters polygons which have a relative height between 'min_ratio' and 'max_ratio'. "
|
||||
"With 'min_included' set to true, the 'min_ratio' value is included in the range, otherwise it's excluded. Same for 'max_included' and 'max_ratio'. "
|
||||
"With 'inverse' set to true, all other polygons will be returned.\n"
|
||||
"\n"
|
||||
"If you don't want to specify a lower or upper limit, pass nil to that parameter.\n"
|
||||
"\n"
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method ("strange_polygon_check", &db::Region::strange_polygon_check,
|
||||
"@brief Returns a region containing those parts of polygons which are \"strange\"\n"
|
||||
"Strange parts of polygons are self-overlapping parts or non-orientable parts (i.e. in the \"8\" configuration).\n"
|
||||
|
|
@ -1877,6 +2014,20 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
|
|||
"This method returns all polygons in self which are not rectangles."
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
) +
|
||||
method_ext ("squares", &squares,
|
||||
"@brief Returns all polygons which are squares\n"
|
||||
"This method returns all polygons in self which are squares."
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method_ext ("non_squares", &non_squares,
|
||||
"@brief Returns all polygons which are not squares\n"
|
||||
"This method returns all polygons in self which are not squares."
|
||||
"Merged semantics applies for this method (see \\merged_semantics= of merged semantics)\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27.\n"
|
||||
) +
|
||||
method_ext ("rectilinear", &rectilinear,
|
||||
"@brief Returns all polygons which are rectilinear\n"
|
||||
"This method returns all polygons in self which are rectilinear."
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ void run_test9 (tl::TestBase *_this, bool deep)
|
|||
db::CompoundRegionSizeOperationNode *result1 = new db::CompoundRegionSizeOperationNode (50, 50, 2, primary);
|
||||
inputs.push_back (result1);
|
||||
|
||||
db::CompoundRegionFilterOperationNode *condition2 = new db::CompoundRegionFilterOperationNode (new db::RectangleFilter (false), primary, true);
|
||||
db::CompoundRegionFilterOperationNode *condition2 = new db::CompoundRegionFilterOperationNode (new db::RectangleFilter (false, false), primary, true);
|
||||
inputs.push_back (condition2);
|
||||
|
||||
db::CompoundRegionSizeOperationNode *result2 = new db::CompoundRegionSizeOperationNode (-50, -50, 2, primary);
|
||||
|
|
@ -536,7 +536,7 @@ void run_test10 (tl::TestBase *_this, bool deep)
|
|||
db::CompoundRegionFilterOperationNode *condition1 = new db::CompoundRegionFilterOperationNode (new db::RegionAreaFilter (0, 10000000, true), primary, true);
|
||||
db::CompoundRegionFilterOperationNode *condition1r = new db::CompoundRegionFilterOperationNode (new db::RegionAreaFilter (0, 10000000, false), primary, true);
|
||||
|
||||
db::CompoundRegionFilterOperationNode *condition2 = new db::CompoundRegionFilterOperationNode (new db::RectangleFilter (false), primary, true);
|
||||
db::CompoundRegionFilterOperationNode *condition2 = new db::CompoundRegionFilterOperationNode (new db::RectangleFilter (false, false), primary, true);
|
||||
|
||||
std::vector<db::CompoundRegionOperationNode *> inputs;
|
||||
inputs.push_back (condition1r);
|
||||
|
|
|
|||
|
|
@ -985,6 +985,58 @@ class DBRegion_TestClass < TestBase
|
|||
|
||||
end
|
||||
|
||||
# Some filters
|
||||
def test_boxfilters1
|
||||
|
||||
r = RBA::Region::new
|
||||
r.insert(RBA::Box::new(0, 0, 1000, 5000))
|
||||
r.insert(RBA::Box::new(3000, 0, 7000, 1000))
|
||||
r.insert(RBA::Box::new(0, 10000, 2000, 12000))
|
||||
|
||||
assert_equal(r.with_bbox_width(1000, false).to_s, "(0,0;0,5000;1000,5000;1000,0)")
|
||||
assert_equal(r.with_bbox_width(1000, true).to_s, "(3000,0;3000,1000;7000,1000;7000,0);(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_bbox_width(1000, 2001, false).to_s, "(0,0;0,5000;1000,5000;1000,0);(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_bbox_width(1000, 2000, false).to_s, "(0,0;0,5000;1000,5000;1000,0)")
|
||||
assert_equal(r.with_bbox_width(1000, 2001, true).to_s, "(3000,0;3000,1000;7000,1000;7000,0)")
|
||||
|
||||
assert_equal(r.with_bbox_height(5000, false).to_s, "(0,0;0,5000;1000,5000;1000,0)")
|
||||
assert_equal(r.with_bbox_height(5000, true).to_s, "(3000,0;3000,1000;7000,1000;7000,0);(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_bbox_height(1000, 2001, false).to_s, "(3000,0;3000,1000;7000,1000;7000,0);(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_bbox_height(1000, 1001, false).to_s, "(3000,0;3000,1000;7000,1000;7000,0)")
|
||||
assert_equal(r.with_bbox_height(1000, 1001, true).to_s, "(0,0;0,5000;1000,5000;1000,0);(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
|
||||
assert_equal(r.with_bbox_aspect_ratio(1.0, false).to_s, "(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_bbox_aspect_ratio(1.0, true).to_s, "(3000,0;3000,1000;7000,1000;7000,0);(0,0;0,5000;1000,5000;1000,0)")
|
||||
assert_equal(r.with_bbox_aspect_ratio(0.9, 1.0, false).to_s, "(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_bbox_aspect_ratio(1.0, 1.1, false).to_s, "(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_bbox_aspect_ratio(0.9, 0.95, false).to_s, "")
|
||||
assert_equal(r.with_bbox_aspect_ratio(0.9, 1.0, false, true, false).to_s, "")
|
||||
assert_equal(r.with_bbox_aspect_ratio(1.0, 1.1, false, false, true).to_s, "")
|
||||
|
||||
assert_equal(r.with_relative_height(1.0, false).to_s, "(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_relative_height(1.0, true).to_s, "(3000,0;3000,1000;7000,1000;7000,0);(0,0;0,5000;1000,5000;1000,0)")
|
||||
assert_equal(r.with_relative_height(0.9, 1.0, false).to_s, "(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_relative_height(1.0, 1.1, false).to_s, "(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_relative_height(0.9, 0.95, false).to_s, "")
|
||||
assert_equal(r.with_relative_height(0.9, 1.0, false, true, false).to_s, "")
|
||||
assert_equal(r.with_relative_height(1.0, 1.1, false, false, true).to_s, "")
|
||||
|
||||
r = RBA::Region::new
|
||||
r.insert(RBA::Box::new(0, 0, 1000, 2000))
|
||||
r.insert(RBA::Box::new(0, 0, 2000, 1000))
|
||||
r.insert(RBA::Box::new(0, 10000, 2000, 12000))
|
||||
|
||||
assert_equal(r.with_area_ratio(1.0, false).to_s, "(0,10000;0,12000;2000,12000;2000,10000)")
|
||||
assert_equal(r.with_area_ratio(1.0, true).to_s, "(0,0;0,2000;1000,2000;1000,1000;2000,1000;2000,0)")
|
||||
assert_equal(r.with_area_ratio(4.0 / 3.0, false).to_s, "(0,0;0,2000;1000,2000;1000,1000;2000,1000;2000,0)")
|
||||
assert_equal(r.with_area_ratio(1.3, 1.4, false).to_s, "(0,0;0,2000;1000,2000;1000,1000;2000,1000;2000,0)")
|
||||
assert_equal(r.with_area_ratio(1.3, 4.0 / 3.0, false, false, true).to_s, "(0,0;0,2000;1000,2000;1000,1000;2000,1000;2000,0)")
|
||||
assert_equal(r.with_area_ratio(1.3, 4.0 / 3.0, false, false, false).to_s, "")
|
||||
assert_equal(r.with_area_ratio(4.0 / 3.0, 1.4, false, true, false).to_s, "(0,0;0,2000;1000,2000;1000,1000;2000,1000;2000,0)")
|
||||
assert_equal(r.with_area_ratio(4.0 / 3.0, 1.4, false, false, false).to_s, "")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
load("test_epilogue.rb")
|
||||
|
|
|
|||
Loading…
Reference in New Issue