Generic filters also for edge pair, edge and text collections

This commit is contained in:
Matthias Koefferlein 2024-01-26 11:49:15 +01:00
parent 2dca4158f2
commit 9fbc926d67
9 changed files with 586 additions and 140 deletions

View File

@ -25,6 +25,7 @@
#define HDR_gsiDeclDbContainerHelpers
#include "dbPropertiesRepository.h"
#include "dbCellVariants.h"
#include "tlVariant.h"
#include "gsiDecl.h"
@ -100,6 +101,143 @@ make_property_methods ()
);
}
// ---------------------------------------------------------------------------------
// Generic shape filter declarations
template <class FilterBase>
class shape_filter_impl
: public FilterBase
{
public:
shape_filter_impl ()
{
mp_vars = &m_mag_and_orient;
m_wants_variants = true;
m_requires_raw_input = false;
}
// overrides virtual method
virtual const db::TransformationReducer *vars () const
{
return mp_vars;
}
// maybe overrides virtual method
virtual bool requires_raw_input () const
{
return m_requires_raw_input;
}
void set_requires_raw_input (bool f)
{
m_requires_raw_input = f;
}
// overrides virtual method
virtual bool wants_variants () const
{
return m_wants_variants;
}
void set_wants_variants (bool f)
{
m_wants_variants = f;
}
void is_isotropic ()
{
mp_vars = &m_mag;
}
void is_scale_invariant ()
{
mp_vars = &m_orientation;
}
void is_isotropic_and_scale_invariant ()
{
mp_vars = 0;
}
static gsi::Methods method_decls (bool with_requires_raw_input)
{
gsi::Methods decls;
if (with_requires_raw_input) {
decls =
method ("requires_raw_input?", &shape_filter_impl::requires_raw_input,
"@brief Gets a value indicating whether the filter needs raw (unmerged) input\n"
"See \\requires_raw_input= for details.\n"
) +
method ("requires_raw_input=", &shape_filter_impl::set_requires_raw_input, gsi::arg ("flag"),
"@brief Sets a value indicating whether the filter needs raw (unmerged) input\n"
"This flag must be set before using this filter. It tells the filter implementation whether the "
"filter wants to have raw input (unmerged). The default value is 'false', meaning that\n"
"the filter will receive merged polygons ('merged semantics').\n"
"\n"
"Setting this value to false potentially saves some CPU time needed for merging the polygons.\n"
"Also, raw input means that strange shapes such as dot-like edges, self-overlapping polygons, "
"empty or degenerated polygons are preserved."
);
}
decls +=
method ("wants_variants?", &shape_filter_impl::wants_variants,
"@brief Gets a value indicating whether the filter prefers cell variants\n"
"See \\wants_variants= for details.\n"
) +
method ("wants_variants=", &shape_filter_impl::set_wants_variants, gsi::arg ("flag"),
"@brief Sets a value indicating whether the filter prefers cell variants\n"
"This flag must be set before using this filter for hierarchical applications (deep mode). "
"It tells the filter implementation whether cell variants should be created (true, the default) "
"or shape propagation will be applied (false).\n"
"\n"
"This decision needs to be made, if the filter indicates that it will deliver different results\n"
"for scaled or rotated versions of the shape (see \\is_isotropic and the other hints). If a cell\n"
"is present with different qualities - as seen from the top cell - the respective instances\n"
"need to be differentiated. Cell variant formation is one way, shape propagation the other way.\n"
"Typically, cell variant formation is less expensive, but the hierarchy will be modified."
) +
method ("is_isotropic", &shape_filter_impl::is_isotropic,
"@brief Indicates that the filter has isotropic properties\n"
"Call this method before using the filter to indicate that the selection is independent of "
"the orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
"hierarchical mode.\n"
"\n"
"Examples for isotropic (polygon) filters are area or perimeter filters. The area or perimeter of a polygon "
"depends on the scale, but not on the orientation of the polygon."
) +
method ("is_scale_invariant", &shape_filter_impl::is_scale_invariant,
"@brief Indicates that the filter is scale invariant\n"
"Call this method before using the filter to indicate that the selection is independent of "
"the scale of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
"hierarchical mode.\n"
"\n"
"An example for a scale invariant (polygon) filter is the bounding box aspect ratio (height/width) filter. "
"The definition of heigh and width depends on the orientation, but the ratio is independent on scale."
) +
method ("is_isotropic_and_scale_invariant", &shape_filter_impl::is_isotropic_and_scale_invariant,
"@brief Indicates that the filter is isotropic and scale invariant\n"
"Call this method before using the filter to indicate that the selection is independent of "
"the scale and orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
"hierarchical mode.\n"
"\n"
"An example for such a (polygon) filter is the square selector. Whether a polygon is a square or not does not depend on "
"the polygon's orientation nor scale."
);
return decls;
}
private:
const db::TransformationReducer *mp_vars;
db::OrientationReducer m_orientation;
db::MagnificationReducer m_mag;
db::MagnificationAndOrientationReducer m_mag_and_orient;
bool m_requires_raw_input;
bool m_wants_variants;
};
}
#endif

View File

@ -36,7 +36,83 @@
namespace gsi
{
static db::EdgePairs *new_v ()
// ---------------------------------------------------------------------------------
// EdgePairFilter binding
class EdgePairFilterImpl
: public shape_filter_impl<db::EdgePairFilterBase>
{
public:
EdgePairFilterImpl () { }
bool issue_selected (const db::EdgePair &) const
{
return false;
}
virtual bool selected (const db::EdgePair &edge_pair) const
{
if (f_selected.can_issue ()) {
return f_selected.issue<EdgePairFilterImpl, bool, const db::EdgePair &> (&EdgePairFilterImpl::issue_selected, edge_pair);
} else {
return db::EdgePairFilterBase::selected (edge_pair);
}
}
gsi::Callback f_selected;
};
Class<gsi::EdgePairFilterImpl> decl_EdgePairFilterImpl ("db", "EdgePairFilter",
EdgePairFilterImpl::method_decls (false) +
callback ("selected", &EdgePairFilterImpl::issue_selected, &EdgePairFilterImpl::f_selected, gsi::arg ("text"),
"@brief Selects an edge pair\n"
"This method is the actual payload. It needs to be reimplemented in a derived class.\n"
"It needs to analyze the edge pair and return 'true' if it should be kept and 'false' if it should be discarded."
),
"@brief A generic edge pair filter adaptor\n"
"\n"
"EdgePair filters are an efficient way to filter edge pairs from a EdgePairs collection. To apply a filter, derive your own "
"filter class and pass an instance to \\EdgePairs#filter or \\EdgePairs#filtered method.\n"
"\n"
"Conceptually, these methods take each edge pair from the collection and present it to the filter's 'selected' method.\n"
"Based on the result of this evaluation, the edge pair is kept or discarded.\n"
"\n"
"The magic happens when deep mode edge pair collections are involved. In that case, the filter will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the filter behaves. You "
"need to configure the filter by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using the filter.\n"
"\n"
"You can skip this step, but the filter algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"Here is some example that filters edge pairs where the edges are perpendicular:"
"\n"
"@code\n"
"class PerpendicularEdgesFilter < RBA::EdgePairFilter\n"
"\n"
" # Constructor\n"
" def initialize\n"
" self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n"
" end\n"
" \n"
" # Select edge pairs where the edges are perpendicular\n"
" def selected(edge_pair)\n"
" return edge_pair.first.d.sprod_sign(edge_pair.second.d) == 0\n"
" end\n"
"\n"
"end\n"
"\n"
"edge_pairs = ... # some EdgePairs object\n"
"perpendicular_only = edge_pairs.filtered(PerpendicularEdgesFilter::new)\n"
"@/code\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// EdgePairs binding
static db::EdgePairs *new_v ()
{
return new db::EdgePairs ();
}
@ -181,6 +257,16 @@ static size_t id (const db::EdgePairs *ep)
return tl::id_of (ep->delegate ());
}
static db::EdgePairs filtered (const db::EdgePairs *r, const EdgePairFilterImpl *f)
{
return r->filtered (*f);
}
static void filter (db::EdgePairs *r, const EdgePairFilterImpl *f)
{
r->filter (*f);
}
static db::EdgePairs with_distance1 (const db::EdgePairs *r, db::EdgePairs::distance_type length, bool inverse)
{
db::EdgePairFilterByDistance ef (length, length + 1, inverse);
@ -619,6 +705,18 @@ Class<db::EdgePairs> decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs",
"The boxes will not be merged, so it is possible to determine overlaps "
"of these boxes for example.\n"
) +
method_ext ("filter", &filter, gsi::arg ("filter"),
"@brief Applies a generic filter in place (replacing the edge pairs from the EdgePair collection)\n"
"See \\EdgePairFilter for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("filtered", &filtered, gsi::arg ("filtered"),
"@brief Applies a generic filter and returns a filtered copy\n"
"See \\EdgePairFilter for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"),
"@brief Filters the edge pairs by length of one of their edges\n"
"Filters the edge pairs in the edge pair collection by length of at least one of their edges. If \"inverse\" is false, only "

View File

@ -37,6 +37,95 @@
namespace gsi
{
// ---------------------------------------------------------------------------------
// EdgeFilter binding
class EdgeFilterImpl
: public shape_filter_impl<db::EdgeFilterBase>
{
public:
EdgeFilterImpl () { }
bool issue_selected (const db::Edge &) const
{
return false;
}
virtual bool selected (const db::Edge &edge) const
{
if (f_selected.can_issue ()) {
return f_selected.issue<EdgeFilterImpl, bool, const db::Edge &> (&EdgeFilterImpl::issue_selected, edge);
} else {
return db::EdgeFilterBase::selected (edge);
}
}
// Returns true if all edges match the criterion
virtual bool selected (const std::unordered_set<db::Edge> &edges) const
{
for (std::unordered_set<db::Edge>::const_iterator e = edges.begin (); e != edges.end (); ++e) {
if (! selected (*e)) {
return false;
}
}
return true;
}
gsi::Callback f_selected;
};
Class<gsi::EdgeFilterImpl> decl_EdgeFilterImpl ("db", "EdgeFilter",
EdgeFilterImpl::method_decls (true) +
callback ("selected", &EdgeFilterImpl::issue_selected, &EdgeFilterImpl::f_selected, gsi::arg ("edge"),
"@brief Selects an edge\n"
"This method is the actual payload. It needs to be reimplemented in a derived class.\n"
"It needs to analyze the edge and return 'true' if it should be kept and 'false' if it should be discarded."
),
"@brief A generic edge filter adaptor\n"
"\n"
"Edge filters are an efficient way to filter edge from a Edges collection. To apply a filter, derive your own "
"filter class and pass an instance to \\Edges#filter or \\Edges#filtered method.\n"
"\n"
"Conceptually, these methods take each edge from the collection and present it to the filter's 'selected' method.\n"
"Based on the result of this evaluation, the edge is kept or discarded.\n"
"\n"
"The magic happens when deep mode edge collections are involved. In that case, the filter will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the filter behaves. You "
"need to configure the filter by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using the filter.\n"
"\n"
"You can skip this step, but the filter algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"Here is some example that filters edges parallel to a given one:"
"\n"
"@code\n"
"class ParallelFilter < RBA::EdgeFilter\n"
"\n"
" # Constructor\n"
" def initialize(ref_edge)\n"
" self.is_scale_invariant # orientation matters, but scale does not\n"
" @ref_edge = ref_edge\n"
" end\n"
" \n"
" # Select only parallel ones\n"
" def selected(edge)\n"
" return edge.is_parallel?(@ref_edge)\n"
" end\n"
"\n"
"end\n"
"\n"
"edges = ... # some Edges object\n"
"ref_edge = ... # some Edge\n"
"parallel_only = edges.filtered(ParallelFilter::new(ref_edge))\n"
"@/code\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// Edges binding
static inline std::vector<db::Edges> as_2edges_vector (const std::pair<db::Edges, db::Edges> &rp)
{
std::vector<db::Edges> res;
@ -204,6 +293,16 @@ static db::Edges moved_xy (const db::Edges *r, db::Coord x, db::Coord y)
return r->transformed (db::Disp (db::Vector (x, y)));
}
static db::Edges filtered (const db::Edges *r, const EdgeFilterImpl *f)
{
return r->filtered (*f);
}
static void filter (db::Edges *r, const EdgeFilterImpl *f)
{
r->filter (*f);
}
static db::Edges with_length1 (const db::Edges *r, db::Edges::distance_type length, bool inverse)
{
db::EdgeLengthFilter f (length, length + 1, inverse);
@ -621,6 +720,18 @@ Class<db::Edges> decl_Edges (decl_dbShapeCollection, "db", "Edges",
"\n"
"This method has been introduced in version 0.26."
) +
method_ext ("filter", &filter, gsi::arg ("filter"),
"@brief Applies a generic filter in place (replacing the edges from the Edges collection)\n"
"See \\EdgeFilter for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("filtered", &filtered, gsi::arg ("filtered"),
"@brief Applies a generic filter and returns a filtered copy\n"
"See \\EdgeFilter for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("with_length", with_length1, gsi::arg ("length"), gsi::arg ("inverse"),
"@brief Filters the edges by length\n"
"Filters the edges in the edge collection by length. If \"inverse\" is false, only "

View File

@ -47,143 +47,6 @@
namespace gsi
{
// ---------------------------------------------------------------------------------
// Generic shape filter declarations
template <class Base>
class shape_filter_impl
: public Base
{
public:
shape_filter_impl ()
{
mp_vars = &m_mag_and_orient;
m_wants_variants = true;
m_requires_raw_input = false;
}
// overrides virtual method
const db::TransformationReducer *vars () const
{
return mp_vars;
}
// maybe overrides virtual method
bool requires_raw_input () const
{
return m_requires_raw_input;
}
void set_requires_raw_input (bool f)
{
m_requires_raw_input = f;
}
// overrides virtual method
bool wants_variants () const
{
return m_wants_variants;
}
void set_wants_variants (bool f)
{
m_wants_variants = f;
}
void is_isotropic ()
{
mp_vars = &m_mag;
}
void is_scale_invariant ()
{
mp_vars = &m_orientation;
}
void is_isotropic_and_scale_invariant ()
{
mp_vars = 0;
}
static gsi::Methods method_decls (bool with_requires_raw_input)
{
gsi::Methods decls;
if (with_requires_raw_input) {
decls =
method ("requires_raw_input?", &shape_filter_impl::requires_raw_input,
"@brief Gets a value indicating whether the filter needs raw (unmerged) input\n"
"See \\requires_raw_input= for details.\n"
) +
method ("requires_raw_input=", &shape_filter_impl::set_requires_raw_input, gsi::arg ("flag"),
"@brief Sets a value indicating whether the filter needs raw (unmerged) input\n"
"This flag must be set before using this filter. It tells the filter implementation whether the "
"filter wants to have raw input (unmerged). The default value is 'false', meaning that\n"
"the filter will receive merged polygons ('merged semantics').\n"
"\n"
"Setting this value to false potentially saves some CPU time needed for merging the polygons.\n"
"Also, raw input means that strange shapes such as dot-like edges, self-overlapping polygons, "
"empty or degenerated polygons are preserved."
);
}
decls +=
method ("wants_variants?", &shape_filter_impl::wants_variants,
"@brief Gets a value indicating whether the filter prefers cell variants\n"
"See \\wants_variants= for details.\n"
) +
method ("wants_variants=", &shape_filter_impl::set_wants_variants, gsi::arg ("flag"),
"@brief Sets a value indicating whether the filter prefers cell variants\n"
"This flag must be set before using this filter for hierarchical applications (deep mode). "
"It tells the filter implementation whether cell variants should be created (true, the default) "
"or shape propagation will be applied (false).\n"
"\n"
"This decision needs to be made, if the filter indicates that it will deliver different results\n"
"for scaled or rotated versions of the shape (see \\is_isotropic and the other hints). If a cell\n"
"is present with different qualities - as seen from the top cell - the respective instances\n"
"need to be differentiated. Cell variant formation is one way, shape propagation the other way.\n"
"Typically, cell variant formation is less expensive, but the hierarchy will be modified."
) +
method ("is_isotropic", &shape_filter_impl::is_isotropic,
"@brief Indicates that the filter has isotropic properties\n"
"Call this method before using the filter to indicate that the selection is independent of "
"the orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
"hierarchical mode.\n"
"\n"
"Examples for isotropic (polygon) filters are area or perimeter filters. The area or perimeter of a polygon "
"depends on the scale, but not on the orientation of the polygon."
) +
method ("is_scale_invariant", &shape_filter_impl::is_scale_invariant,
"@brief Indicates that the filter is scale invariant\n"
"Call this method before using the filter to indicate that the selection is independent of "
"the scale of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
"hierarchical mode.\n"
"\n"
"An example for a scale invariant (polygon) filter is the bounding box aspect ratio (height/width) filter. "
"The definition of heigh and width depends on the orientation, but the ratio is independent on scale."
) +
method ("is_isotropic_and_scale_invariant", &shape_filter_impl::is_isotropic_and_scale_invariant,
"@brief Indicates that the filter is isotropic and scale invariant\n"
"Call this method before using the filter to indicate that the selection is independent of "
"the scale and orientation of the shape. This helps the filter algorithm optimizing the filter run, specifically in "
"hierarchical mode.\n"
"\n"
"An example for such a (polygon) filter is the square selector. Whether a polygon is a square or not does not depend on "
"the polygon's orientation nor scale."
);
return decls;
}
private:
const db::TransformationReducer *mp_vars;
db::OrientationReducer m_orientation;
db::MagnificationReducer m_mag;
db::MagnificationAndOrientationReducer m_mag_and_orient;
bool m_requires_raw_input;
bool m_wants_variants;
};
// ---------------------------------------------------------------------------------
// PolygonFilter binding
@ -217,7 +80,7 @@ public:
gsi::Callback f_selected;
};
Class<gsi::PolygonFilterImpl> decl_PluginFactory ("db", "PolygonFilter",
Class<gsi::PolygonFilterImpl> decl_PolygonFilterImpl ("db", "PolygonFilter",
PolygonFilterImpl::method_decls (true) +
callback ("selected", &PolygonFilterImpl::issue_selected, &PolygonFilterImpl::f_selected, gsi::arg ("polygon"),
"@brief Selects a polygon\n"
@ -264,7 +127,6 @@ Class<gsi::PolygonFilterImpl> decl_PluginFactory ("db", "PolygonFilter",
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// Region binding

View File

@ -33,6 +33,83 @@
namespace gsi
{
// ---------------------------------------------------------------------------------
// TextFilter binding
class TextFilterImpl
: public shape_filter_impl<db::TextFilterBase>
{
public:
TextFilterImpl () { }
bool issue_selected (const db::Text &) const
{
return false;
}
virtual bool selected (const db::Text &text) const
{
if (f_selected.can_issue ()) {
return f_selected.issue<TextFilterImpl, bool, const db::Text &> (&TextFilterImpl::issue_selected, text);
} else {
return db::TextFilterBase::selected (text);
}
}
gsi::Callback f_selected;
};
Class<gsi::TextFilterImpl> decl_TextFilterImpl ("db", "TextFilter",
TextFilterImpl::method_decls (false) +
callback ("selected", &TextFilterImpl::issue_selected, &TextFilterImpl::f_selected, gsi::arg ("text"),
"@brief Selects a text\n"
"This method is the actual payload. It needs to be reimplemented in a derived class.\n"
"It needs to analyze the text and return 'true' if it should be kept and 'false' if it should be discarded."
),
"@brief A generic text filter adaptor\n"
"\n"
"Text filters are an efficient way to filter texts from a Texts collection. To apply a filter, derive your own "
"filter class and pass an instance to \\Texts#filter or \\Texts#filtered method.\n"
"\n"
"Conceptually, these methods take each text from the collection and present it to the filter's 'selected' method.\n"
"Based on the result of this evaluation, the text is kept or discarded.\n"
"\n"
"The magic happens when deep mode text collections are involved. In that case, the filter will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the filter behaves. You "
"need to configure the filter by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using the filter.\n"
"\n"
"You can skip this step, but the filter algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"Here is some example that filters texts with a given string length:"
"\n"
"@code\n"
"class TextStringLengthFilter < RBA::TextFilter\n"
"\n"
" # Constructor\n"
" def initialize(string_length)\n"
" self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n"
" @string_length = string_length\n"
" end\n"
" \n"
" # Select texts with given string length\n"
" def selected(text)\n"
" return text.string.size == @string_length\n"
" end\n"
"\n"
"end\n"
"\n"
"texts = ... # some Texts object\n"
"with_length_3 = edges.filtered(TextStringLengthFilter::new(3))\n"
"@/code\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// Texts binding
static db::Texts *new_v ()
{
return new db::Texts ();
@ -152,6 +229,16 @@ static size_t id (const db::Texts *t)
return tl::id_of (t->delegate ());
}
static db::Texts filtered (const db::Texts *r, const TextFilterImpl *f)
{
return r->filtered (*f);
}
static void filter (db::Texts *r, const TextFilterImpl *f)
{
r->filter (*f);
}
static db::Texts with_text (const db::Texts *r, const std::string &text, bool inverse)
{
db::TextStringFilter f (text, inverse);
@ -401,6 +488,18 @@ Class<db::Texts> decl_Texts (decl_dbShapeCollection, "db", "Texts",
"@brief Converts the edge pairs to polygons\n"
"This method creates polygons from the texts. This is equivalent to calling \\extents."
) +
method_ext ("filter", &filter, gsi::arg ("filter"),
"@brief Applies a generic filter in place (replacing the texts from the Texts collection)\n"
"See \\TextFilter for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("filtered", &filtered, gsi::arg ("filtered"),
"@brief Applies a generic filter and returns a filtered copy\n"
"See \\TextFilter for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("with_text", with_text, gsi::arg ("text"), gsi::arg ("inverse"),
"@brief Filter the text by text string\n"
"If \"inverse\" is false, this method returns the texts with the given string.\n"

View File

@ -30,6 +30,20 @@ def csort(s)
s.split(/(?<=\));(?=\()/).sort.join(";")
end
class PerpendicularEdgesFilter < RBA::EdgePairFilter
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # orientation and scale do not matter
end
# Select edge pairs where the edges are perpendicular
def selected(edge_pair)
return edge_pair.first.d.sprod_sign(edge_pair.second.d) == 0
end
end
class DBEdgePairs_TestClass < TestBase
# Basics
@ -331,6 +345,35 @@ class DBEdgePairs_TestClass < TestBase
end
# Generic filters
def test_generic_filters
# Some basic tests for the filter class
f = PerpendicularEdgesFilter::new
assert_equal(f.wants_variants?, true)
f.wants_variants = false
assert_equal(f.wants_variants?, false)
# Smoke test
f.is_isotropic
f.is_scale_invariant
# Some application
f = PerpendicularEdgesFilter::new
edge_pairs = RBA::EdgePairs::new
edge_pairs.insert(RBA::EdgePair::new([0, 0, 100, 0], [0, 100, 0, 300 ]))
edge_pairs.insert(RBA::EdgePair::new([200, 0, 300, 0], [200, 100, 220, 300 ]))
assert_equal(edge_pairs.filtered(f).to_s, "(0,0;100,0)/(0,100;0,300)")
assert_equal(edge_pairs.to_s, "(0,0;100,0)/(0,100;0,300);(200,0;300,0)/(200,100;220,300)")
edge_pairs.filter(f)
assert_equal(edge_pairs.to_s, "(0,0;100,0)/(0,100;0,300)")
end
end

View File

@ -30,6 +30,21 @@ def csort(s)
s.split(/(?<=\));(?=\()/).sort.join(";")
end
class ParallelFilter < RBA::EdgeFilter
# Constructor
def initialize(ref_edge)
self.is_scale_invariant # orientation matters, but scale does not
@ref_edge = ref_edge
end
# Select only parallel ones
def selected(edge)
return edge.is_parallel?(@ref_edge)
end
end
class DBEdges_TestClass < TestBase
# Basics
@ -749,6 +764,38 @@ class DBEdges_TestClass < TestBase
end
# Generic filters
def test_generic_filters
# Some basic tests for the filter class
f = ParallelFilter::new(RBA::Edge::new(0, 0, 100, 100))
assert_equal(f.wants_variants?, true)
f.wants_variants = false
assert_equal(f.wants_variants?, false)
assert_equal(f.requires_raw_input, false)
f.requires_raw_input = false
assert_equal(f.requires_raw_input, false)
# Smoke test
f.is_isotropic
f.is_scale_invariant
# Some application
f = ParallelFilter::new(RBA::Edge::new(0, 0, 100, 100))
edges = RBA::Edges::new
edges.insert(RBA::Edge::new(100, 0, 200, 100))
edges.insert(RBA::Edge::new(100, 100, 100, 200))
assert_equal(edges.filtered(f).to_s, "(100,0;200,100)")
assert_equal(edges.to_s, "(100,0;200,100);(100,100;100,200)")
edges.filter(f)
assert_equal(edges.to_s, "(100,0;200,100)")
end
end
load("test_epilogue.rb")

View File

@ -1227,6 +1227,9 @@ class DBRegion_TestClass < TestBase
region.insert(RBA::Box::new(200, 0, 300, 100))
assert_equal(region.filtered(TriangleFilter::new).to_s, "(0,0;100,100;100,0)")
assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)")
region.filter(TriangleFilter::new)
assert_equal(region.to_s, "(0,0;100,100;100,0)")
end

View File

@ -30,6 +30,21 @@ def csort(s)
s.split(/(?<=\));(?=\()/).sort.join(";")
end
class TextStringLengthFilter < RBA::TextFilter
# Constructor
def initialize(string_length)
self.is_isotropic_and_scale_invariant # orientation and scale do not matter
@string_length = string_length
end
# Select texts with given string length
def selected(text)
return text.string.size == @string_length
end
end
class DBTexts_TestClass < TestBase
# Basics
@ -324,6 +339,36 @@ class DBTexts_TestClass < TestBase
end
# Generic filters
def test_generic_filters
# Some basic tests for the filter class
f = TextStringLengthFilter::new(3)
assert_equal(f.wants_variants?, true)
f.wants_variants = false
assert_equal(f.wants_variants?, false)
# Smoke test
f.is_isotropic
f.is_scale_invariant
# Some application
f = TextStringLengthFilter::new(3)
texts = RBA::Texts::new
texts.insert(RBA::Text::new("long", [ RBA::Trans::M45, 10, 0 ]))
texts.insert(RBA::Text::new("tla", [ RBA::Trans::R0, 0, 0 ]))
texts.insert(RBA::Text::new("00", [ RBA::Trans::R90, 0, 20 ]))
assert_equal(texts.filtered(f).to_s, "('tla',r0 0,0)")
assert_equal(texts.to_s, "('long',m45 10,0);('tla',r0 0,0);('00',r90 0,20)")
texts.filter(f)
assert_equal(texts.to_s, "('tla',r0 0,0)")
end
end