mirror of https://github.com/KLayout/klayout.git
Generic filters also for edge pair, edge and text collections
This commit is contained in:
parent
2dca4158f2
commit
9fbc926d67
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue