diff --git a/src/db/db/dbAsIfFlatEdgePairs.cc b/src/db/db/dbAsIfFlatEdgePairs.cc index cf9c3621b..d0f098e27 100644 --- a/src/db/db/dbAsIfFlatEdgePairs.cc +++ b/src/db/db/dbAsIfFlatEdgePairs.cc @@ -147,6 +147,24 @@ void AsIfFlatEdgePairs::invalidate_bbox () m_bbox_valid = false; } +EdgePairsDelegate * +AsIfFlatEdgePairs::processed (const EdgePairProcessorBase &filter) const +{ + std::unique_ptr edge_pairs (new FlatEdgePairs ()); + + std::vector res_edge_pairs; + + for (EdgePairsIterator e = begin (); ! e.at_end (); ++e) { + res_edge_pairs.clear (); + filter.process (*e, res_edge_pairs); + for (std::vector::const_iterator er = res_edge_pairs.begin (); er != res_edge_pairs.end (); ++er) { + edge_pairs->insert (*er); + } + } + + return edge_pairs.release (); +} + RegionDelegate * AsIfFlatEdgePairs::processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbAsIfFlatEdgePairs.h b/src/db/db/dbAsIfFlatEdgePairs.h index 29f18f6f1..2a69bb035 100644 --- a/src/db/db/dbAsIfFlatEdgePairs.h +++ b/src/db/db/dbAsIfFlatEdgePairs.h @@ -53,8 +53,14 @@ public: virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &) const; - virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const; - virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const; + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &proc) + { + return processed (proc); + } + + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &proc) const; + virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &proc) const; + virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &proc) const; virtual EdgePairsDelegate *add_in_place (const EdgePairs &other) { diff --git a/src/db/db/dbAsIfFlatTexts.cc b/src/db/db/dbAsIfFlatTexts.cc index 0e38590a6..8c73f1c1a 100644 --- a/src/db/db/dbAsIfFlatTexts.cc +++ b/src/db/db/dbAsIfFlatTexts.cc @@ -164,6 +164,24 @@ AsIfFlatTexts::filtered (const TextFilterBase &filter) const return new_texts.release (); } +TextsDelegate * +AsIfFlatTexts::processed (const TextProcessorBase &filter) const +{ + std::unique_ptr texts (new FlatTexts ()); + + std::vector res_texts; + + for (TextsIterator e = begin (); ! e.at_end (); ++e) { + res_texts.clear (); + filter.process (*e, res_texts); + for (std::vector::const_iterator er = res_texts.begin (); er != res_texts.end (); ++er) { + texts->insert (*er); + } + } + + return texts.release (); +} + RegionDelegate * AsIfFlatTexts::processed_to_polygons (const TextToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbAsIfFlatTexts.h b/src/db/db/dbAsIfFlatTexts.h index ffff931e1..07c023741 100644 --- a/src/db/db/dbAsIfFlatTexts.h +++ b/src/db/db/dbAsIfFlatTexts.h @@ -55,6 +55,12 @@ public: virtual TextsDelegate *filtered (const TextFilterBase &) const; + virtual TextsDelegate *process_in_place (const TextProcessorBase &proc) + { + return processed (proc); + } + + virtual TextsDelegate *processed (const TextProcessorBase &proc) const; virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const; virtual TextsDelegate *add_in_place (const Texts &other) diff --git a/src/db/db/dbDeepEdgePairs.cc b/src/db/db/dbDeepEdgePairs.cc index d19a08fa2..090644265 100644 --- a/src/db/db/dbDeepEdgePairs.cc +++ b/src/db/db/dbDeepEdgePairs.cc @@ -451,6 +451,18 @@ DeepEdgePairs::apply_filter (const EdgePairFilterBase &filter) const return res.release (); } +EdgePairsDelegate *DeepEdgePairs::process_in_place (const EdgePairProcessorBase &filter) +{ + // TODO: implement to be really in-place + return processed (filter); +} + +EdgePairsDelegate * +DeepEdgePairs::processed (const EdgePairProcessorBase &filter) const +{ + return shape_collection_processed_impl (deep_layer (), filter); +} + RegionDelegate * DeepEdgePairs::processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbDeepEdgePairs.h b/src/db/db/dbDeepEdgePairs.h index ccc73a762..8ec686da0 100644 --- a/src/db/db/dbDeepEdgePairs.h +++ b/src/db/db/dbDeepEdgePairs.h @@ -78,6 +78,8 @@ public: virtual EdgePairsDelegate *filter_in_place (const EdgePairFilterBase &filter); virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &) const; + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &); + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &) const; virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const; virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const; diff --git a/src/db/db/dbDeepRegion.h b/src/db/db/dbDeepRegion.h index 1bef5efb1..229b2e696 100644 --- a/src/db/db/dbDeepRegion.h +++ b/src/db/db/dbDeepRegion.h @@ -182,8 +182,6 @@ private: std::pair and_and_not_with (const DeepRegion *other, PropertyConstraint property_constraint) const; DeepRegion *apply_filter (const PolygonFilterBase &filter) const; - template OutputContainer *processed_impl (const polygon_processor &filter) const; - template void configure_proc (Proc &proc) const { diff --git a/src/db/db/dbDeepTexts.cc b/src/db/db/dbDeepTexts.cc index f328452ea..ebf6daccd 100644 --- a/src/db/db/dbDeepTexts.cc +++ b/src/db/db/dbDeepTexts.cc @@ -474,6 +474,18 @@ DeepTexts *DeepTexts::apply_filter (const TextFilterBase &filter) const return res.release (); } +TextsDelegate *DeepTexts::process_in_place (const TextProcessorBase &filter) +{ + // TODO: implement to be really in-place + return processed (filter); +} + +TextsDelegate * +DeepTexts::processed (const TextProcessorBase &filter) const +{ + return shape_collection_processed_impl (deep_layer (), filter); +} + RegionDelegate * DeepTexts::processed_to_polygons (const TextToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbDeepTexts.h b/src/db/db/dbDeepTexts.h index 1bd5bb66c..44c367e07 100644 --- a/src/db/db/dbDeepTexts.h +++ b/src/db/db/dbDeepTexts.h @@ -80,6 +80,8 @@ public: virtual TextsDelegate *filter_in_place (const TextFilterBase &filter); virtual TextsDelegate *filtered (const TextFilterBase &) const; + virtual TextsDelegate *process_in_place (const TextProcessorBase &); + virtual TextsDelegate *processed (const TextProcessorBase &) const; virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const; virtual TextsDelegate *add_in_place (const Texts &other); diff --git a/src/db/db/dbEdgePairs.cc b/src/db/db/dbEdgePairs.cc index 0451006ef..fcfd405f9 100644 --- a/src/db/db/dbEdgePairs.cc +++ b/src/db/db/dbEdgePairs.cc @@ -169,14 +169,19 @@ EdgePairs::properties_repository () return *r; } -void EdgePairs::processed (Region &output, const EdgePairToPolygonProcessorBase &filter) const +EdgePairs EdgePairs::processed (const EdgePairProcessorBase &proc) const { - output = Region (mp_delegate->processed_to_polygons (filter)); + return EdgePairs (mp_delegate->processed (proc)); } -void EdgePairs::processed (Edges &output, const EdgePairToEdgeProcessorBase &filter) const +void EdgePairs::processed (Region &output, const EdgePairToPolygonProcessorBase &proc) const { - output = Edges (mp_delegate->processed_to_edges (filter)); + output = Region (mp_delegate->processed_to_polygons (proc)); +} + +void EdgePairs::processed (Edges &output, const EdgePairToEdgeProcessorBase &proc) const +{ + output = Edges (mp_delegate->processed_to_edges (proc)); } void EdgePairs::polygons (Region &output, db::Coord e) const diff --git a/src/db/db/dbEdgePairs.h b/src/db/db/dbEdgePairs.h index 38e32d499..9d1b1aee2 100644 --- a/src/db/db/dbEdgePairs.h +++ b/src/db/db/dbEdgePairs.h @@ -328,13 +328,31 @@ public: return EdgePairs (mp_delegate->filtered (filter)); } + /** + * @brief Processes the edge pairs in-place + * + * This method will run the processor over all edge pairs and replace the collection by the results. + */ + EdgePairs &process (const EdgePairProcessorBase &proc) + { + set_delegate (mp_delegate->process_in_place (proc)); + return *this; + } + + /** + * @brief Processes the edge pairs + * + * This method will run the processor over all edge pairs return a new edge pair collection with the results. + */ + EdgePairs processed (const EdgePairProcessorBase &proc) const; + /** * @brief Processes the edge pairs into polygons * * This method will run the processor over all edge pairs and return a region * with the outputs of the processor. */ - void processed (Region &output, const EdgePairToPolygonProcessorBase &filter) const; + void processed (Region &output, const EdgePairToPolygonProcessorBase &proc) const; /** * @brief Processes the edge pairs into edges @@ -342,7 +360,7 @@ public: * This method will run the processor over all edge pairs and return a edge collection * with the outputs of the processor. */ - void processed (Edges &output, const EdgePairToEdgeProcessorBase &filter) const; + void processed (Edges &output, const EdgePairToEdgeProcessorBase &proc) const; /** * @brief Transforms the edge pair set diff --git a/src/db/db/dbEdgePairsDelegate.h b/src/db/db/dbEdgePairsDelegate.h index 32fa03670..189ff293e 100644 --- a/src/db/db/dbEdgePairsDelegate.h +++ b/src/db/db/dbEdgePairsDelegate.h @@ -39,6 +39,7 @@ class RegionDelegate; class EdgesDelegate; class Layout; +typedef shape_collection_processor EdgePairProcessorBase; typedef shape_collection_processor EdgePairToPolygonProcessorBase; typedef shape_collection_processor EdgePairToEdgeProcessorBase; @@ -194,8 +195,10 @@ public: virtual EdgePairsDelegate *filter_in_place (const EdgePairFilterBase &filter) = 0; virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &filter) const = 0; - virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const = 0; - virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const = 0; + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &proc) = 0; + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &proc) const = 0; + virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &proc) const = 0; + virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &proc) const = 0; virtual RegionDelegate *polygons (db::Coord e) const = 0; virtual EdgesDelegate *edges () const = 0; diff --git a/src/db/db/dbEmptyEdgePairs.h b/src/db/db/dbEmptyEdgePairs.h index 6680ec6a3..16be5fa99 100644 --- a/src/db/db/dbEmptyEdgePairs.h +++ b/src/db/db/dbEmptyEdgePairs.h @@ -56,6 +56,8 @@ public: virtual EdgePairsDelegate *filter_in_place (const EdgePairFilterBase &) { return this; } virtual EdgePairsDelegate *filtered (const EdgePairFilterBase &) const { return new EmptyEdgePairs (); } + virtual EdgePairsDelegate *process_in_place (const EdgePairProcessorBase &) { return this; } + virtual EdgePairsDelegate *processed (const EdgePairProcessorBase &) const { return new EmptyEdgePairs (); } virtual RegionDelegate *processed_to_polygons (const EdgePairToPolygonProcessorBase &filter) const; virtual EdgesDelegate *processed_to_edges (const EdgePairToEdgeProcessorBase &filter) const; diff --git a/src/db/db/dbEmptyTexts.h b/src/db/db/dbEmptyTexts.h index c224dadaa..9bd206de9 100644 --- a/src/db/db/dbEmptyTexts.h +++ b/src/db/db/dbEmptyTexts.h @@ -57,6 +57,8 @@ public: virtual TextsDelegate *filter_in_place (const TextFilterBase &) { return this; } virtual TextsDelegate *filtered (const TextFilterBase &) const { return new EmptyTexts (); } + virtual TextsDelegate *process_in_place (const TextProcessorBase &) { return this; } + virtual TextsDelegate *processed (const TextProcessorBase &) const { return new EmptyTexts (); } virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &) const; virtual RegionDelegate *polygons (db::Coord e) const; diff --git a/src/db/db/dbRegionDelegate.h b/src/db/db/dbRegionDelegate.h index ab8597646..b1c32aad0 100644 --- a/src/db/db/dbRegionDelegate.h +++ b/src/db/db/dbRegionDelegate.h @@ -106,62 +106,6 @@ public: virtual bool wants_variants () const = 0; }; -/** - * @brief A template base class for polygon processors - * - * A polygon processor can turn a polygon into something else. - */ -template -class DB_PUBLIC polygon_processor -{ -public: - /** - * @brief Constructor - */ - polygon_processor () { } - - /** - * @brief Destructor - */ - virtual ~polygon_processor () { } - - /** - * @brief Performs the actual processing - * This method will take the input polygon from "polygon" and puts the results into "res". - * "res" can be empty - in this case, the polygon will be skipped. - */ - virtual void process (const db::Polygon &polygon, std::vector &res) const = 0; - - /** - * @brief Returns the transformation reducer for building cell variants - * This method may return 0. In this case, not cell variants are built. - */ - virtual const TransformationReducer *vars () const = 0; - - /** - * @brief Returns true, if the result of this operation can be regarded "merged" always. - */ - virtual bool result_is_merged () const = 0; - - /** - * @brief Returns true, if the result of this operation must not be merged. - * This feature can be used, if the result represents "degenerated" objects such - * as point-like edges. These must not be merged. Otherwise they disappear. - */ - virtual bool result_must_not_be_merged () const = 0; - - /** - * @brief Returns true, if the processor wants raw (not merged) input - */ - virtual bool requires_raw_input () const = 0; - - /** - * @brief Returns true, if the processor wants to build variants - * If not true, the processor accepts shape propagation as variant resolution. - */ - virtual bool wants_variants () const = 0; -}; - typedef shape_collection_processor PolygonProcessorBase; typedef shape_collection_processor PolygonToEdgeProcessorBase; typedef shape_collection_processor PolygonToEdgePairProcessorBase; diff --git a/src/db/db/dbShapeCollectionUtils.h b/src/db/db/dbShapeCollectionUtils.h index 0d2a448f9..c300bb052 100644 --- a/src/db/db/dbShapeCollectionUtils.h +++ b/src/db/db/dbShapeCollectionUtils.h @@ -48,6 +48,9 @@ class DB_PUBLIC_TEMPLATE shape_collection_processor : public tl::Object { public: + typedef Shape shape_type; + typedef Result result_type; + /** * @brief Constructor */ diff --git a/src/db/db/dbTexts.cc b/src/db/db/dbTexts.cc index 31d6ae0a8..0b90c8897 100644 --- a/src/db/db/dbTexts.cc +++ b/src/db/db/dbTexts.cc @@ -199,6 +199,11 @@ MutableTexts *Texts::mutable_texts () return texts; } +Texts Texts::processed (const TextProcessorBase &proc) const +{ + return Texts (mp_delegate->processed (proc)); +} + void Texts::processed (Region &output, const TextToPolygonProcessorBase &filter) const { output = Region (mp_delegate->processed_to_polygons (filter)); diff --git a/src/db/db/dbTexts.h b/src/db/db/dbTexts.h index 3ed3cf1f8..82d017187 100644 --- a/src/db/db/dbTexts.h +++ b/src/db/db/dbTexts.h @@ -316,7 +316,25 @@ public: } /** - * @brief Processes the edges into polygons + * @brief Processes the edge pairs in-place + * + * This method will run the processor over all texts and replace the collection by the results. + */ + Texts &process (const TextProcessorBase &proc) + { + set_delegate (mp_delegate->process_in_place (proc)); + return *this; + } + + /** + * @brief Processes the texts + * + * This method will run the processor over all texts and return a new text collection with the results. + */ + Texts processed (const TextProcessorBase &proc) const; + + /** + * @brief Processes the texts into polygons * * This method will run the processor over all edges and return a region * with the outputs of the processor. diff --git a/src/db/db/dbTextsDelegate.h b/src/db/db/dbTextsDelegate.h index 6a818cec5..05f26188e 100644 --- a/src/db/db/dbTextsDelegate.h +++ b/src/db/db/dbTextsDelegate.h @@ -40,6 +40,7 @@ class RegionDelegate; class EdgesDelegate; class Layout; +typedef shape_collection_processor TextProcessorBase; typedef shape_collection_processor TextToPolygonProcessorBase; typedef db::generic_shape_iterator_delegate_base TextsIteratorDelegate; @@ -94,7 +95,9 @@ public: virtual TextsDelegate *filter_in_place (const TextFilterBase &filter) = 0; virtual TextsDelegate *filtered (const TextFilterBase &filter) const = 0; - virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const = 0; + virtual TextsDelegate *process_in_place (const TextProcessorBase &proc) = 0; + virtual TextsDelegate *processed (const TextProcessorBase &proc) const = 0; + virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &proc) const = 0; virtual RegionDelegate *polygons (db::Coord e) const = 0; virtual EdgesDelegate *edges () const = 0; diff --git a/src/db/db/gsiDeclDbContainerHelpers.h b/src/db/db/gsiDeclDbContainerHelpers.h index e16e91be8..7c78def79 100644 --- a/src/db/db/gsiDeclDbContainerHelpers.h +++ b/src/db/db/gsiDeclDbContainerHelpers.h @@ -25,6 +25,7 @@ #define HDR_gsiDeclDbContainerHelpers #include "dbPropertiesRepository.h" +#include "dbCellVariants.h" #include "tlVariant.h" #include "gsiDecl.h" @@ -100,6 +101,361 @@ make_property_methods () ); } +// --------------------------------------------------------------------------------- +// Generic shape filter declarations + +template +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; +}; + +// --------------------------------------------------------------------------------- +// Generic shape processor declarations + +template +class shape_processor_impl + : public ProcessorBase +{ +public: + typedef typename ProcessorBase::shape_type shape_type; + typedef typename ProcessorBase::result_type result_type; + + shape_processor_impl () + { + mp_vars = &m_mag_and_orient; + m_wants_variants = true; + m_requires_raw_input = false; + m_result_is_merged = false; + m_result_must_not_be_merged = 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; + } + + // overrides virtual method + virtual bool result_is_merged () const + { + return m_result_is_merged; + } + + void set_result_is_merged (bool f) + { + m_result_is_merged = f; + } + + // overrides virtual method + virtual bool result_must_not_be_merged () const + { + return m_result_must_not_be_merged; + } + + void set_result_must_not_be_merged (bool f) + { + m_result_must_not_be_merged = 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; + } + + virtual void process (const shape_type &shape, std::vector &res) const + { + res = do_process (shape); + } + + std::vector issue_do_process (const shape_type &) const + { + return std::vector (); + } + + std::vector do_process (const shape_type &shape) const + { + if (f_process.can_issue ()) { + return f_process.issue, const shape_type &> (&shape_processor_impl::issue_do_process, shape); + } else { + return issue_do_process (shape); + } + } + + gsi::Callback f_process; + + static gsi::Methods method_decls (bool with_merged_options) + { + gsi::Methods decls = + callback ("process", &shape_processor_impl::issue_do_process, &shape_processor_impl::f_process, gsi::arg ("shape"), + "@brief Processes a shape\n" + "This method is the actual payload. It needs to be reimplemented in a derived class.\n" + "If needs to process the input shape and deliver a list of output shapes.\n" + "The output list may be empty to entirely discard the input shape. It may also contain more than a single shape.\n" + "In that case, the number of total shapes may grow during application of the processor.\n" + ); + + if (with_merged_options) { + decls += + method ("requires_raw_input?", &shape_processor_impl::requires_raw_input, + "@brief Gets a value indicating whether the processor needs raw (unmerged) input\n" + "See \\requires_raw_input= for details.\n" + ) + + method ("requires_raw_input=", &shape_processor_impl::set_requires_raw_input, gsi::arg ("flag"), + "@brief Sets a value indicating whether the processor needs raw (unmerged) input\n" + "This flag must be set before using this processor. It tells the processor implementation whether the " + "processor wants to have raw input (unmerged). The default value is 'false', meaning that\n" + "the processor 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." + ) + + method ("result_is_merged?", &shape_processor_impl::result_is_merged, + "@brief Gets a value indicating whether the processor delivers merged output\n" + "See \\result_is_merged= for details.\n" + ) + + method ("result_is_merged=", &shape_processor_impl::set_result_is_merged, gsi::arg ("flag"), + "@brief Sets a value indicating whether the processor delivers merged output\n" + "This flag must be set before using this processor. If the processor maintains the merged condition\n" + "by design (output is merged if input is), it is a good idea to set this predicate to 'true'.\n" + "This will avoid additional merge steps when the resulting collection is used in further operations\n" + "that need merged input\n." + ) + + method ("result_must_not_be_merged?", &shape_processor_impl::result_must_not_be_merged, + "@brief Gets a value indicating whether the processor's output must not be merged\n" + "See \\result_must_not_be_merged= for details.\n" + ) + + method ("result_must_not_be_merged=", &shape_processor_impl::set_result_must_not_be_merged, gsi::arg ("flag"), + "@brief Sets a value indicating whether the processor's output must not be merged\n" + "This flag must be set before using this processor. The processor can set this flag if it wants to\n" + "deliver shapes that must not be merged - e.g. point-like edges or strange or degenerated polygons.\n." + ); + } + + decls += + method ("wants_variants?", &shape_processor_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_processor_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_processor_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) processors are size or shrink operators. Size or shrink is not dependent " + "on orientation unless size or shrink needs to be different in x and y direction." + ) + + method ("is_scale_invariant", &shape_processor_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) processor is the rotation operator. Rotation is not depending on scale, " + "but on the original orientation as mirrored versions need to be rotated differently." + ) + + method ("is_isotropic_and_scale_invariant", &shape_processor_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) processor is the convex decomposition operator. The decomposition of a polygon into " + "convex parts is an operation that is not depending on scale nor orientation." + ); + + 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; + bool m_result_is_merged; + bool m_result_must_not_be_merged; + + // No copying + shape_processor_impl &operator= (const shape_processor_impl &); + shape_processor_impl (const shape_processor_impl &); +}; + } #endif diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 870991c4d..4a22f7045 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -36,7 +36,182 @@ namespace gsi { -static db::EdgePairs *new_v () +// --------------------------------------------------------------------------------- +// EdgePairFilter binding + +class EdgePairFilterImpl + : public shape_filter_impl +{ +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::issue_selected, edge_pair); + } else { + return issue_selected (edge_pair); + } + } + + gsi::Callback f_selected; + +private: + // No copying + EdgePairFilterImpl &operator= (const EdgePairFilterImpl &); + EdgePairFilterImpl (const EdgePairFilterImpl &); +}; + +Class 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" +); + +// --------------------------------------------------------------------------------- +// EdgePairProcessor binding + +Class > decl_EdgePairProcessor ("db", "EdgePairOperator", + shape_processor_impl::method_decls (false), + "@brief A generic edge-pair operator\n" + "\n" + "Edge pair processors are an efficient way to process edge pairs from an edge pair collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\EdgePairs#processed or \\EdgePairs#process method.\n" + "\n" + "Conceptually, these methods take each edge pair from the edge pair collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edge pairs derived from the input edge pair.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 flips the edge pairs (swaps first and second edge):" + "\n" + "@code\n" + "class FlipEdgePairs < RBA::EdgePairOperator\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n" + " end\n" + " \n" + " # Flips the edge pair\n" + " def process(edge_pair)\n" + " return [ RBA::EdgePair::new(edge_pair.second, edge_pair.first) ]\n" + " end\n" + "\n" + "end\n" + "\n" + "edge_pairs = ... # some EdgePairs object\n" + "flipped = edge_pairs.processed(FlipEdgePairs::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgePairToPolygonProcessor ("db", "EdgePairToPolygonOperator", + shape_processor_impl::method_decls (false), + "@brief A generic edge-pair-to-polygon operator\n" + "\n" + "Edge pair processors are an efficient way to process edge pairs from an edge pair collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\EdgePairs#processed method.\n" + "\n" + "Conceptually, these methods take each edge pair from the edge pair collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input edge pair.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeToPolygonOperator class, with the exception that this incarnation receives edge pairs.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgePairToEdgeProcessor ("db", "EdgePairToEdgeOperator", + shape_processor_impl::method_decls (false), + "@brief A generic edge-pair-to-edge operator\n" + "\n" + "Edge processors are an efficient way to process edge pairs from an edge pair collection. To apply a processor, derive your own " + "operator class and pass an instance to \\EdgePairs#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edges derived from the input edge pair.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeToEdgePairOperator class, with the exception that this incarnation has to deliver edges and takes edge pairs.\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 +356,40 @@ 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 processed_epep (const db::EdgePairs *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_epep (db::EdgePairs *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::Edges processed_epe (const db::EdgePairs *r, const shape_processor_impl *f) +{ + db::Edges out; + r->processed (out, *f); + return out; +} + +static db::Region processed_epp (const db::EdgePairs *r, const shape_processor_impl *f) +{ + db::Region out; + r->processed (out, *f); + return out; +} + 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 +828,42 @@ Class 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 ("process", &process_epep, gsi::arg ("process"), + "@brief Applies a generic edge pair processor in place (replacing the edge pairs from the EdgePairs collection)\n" + "See \\EdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_epep, gsi::arg ("processed"), + "@brief Applies a generic edge pair processor and returns a processed copy\n" + "See \\EdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_epe, gsi::arg ("processed"), + "@brief Applies a generic edge-pair-to-edge processor and returns an edge collection with the results\n" + "See \\EdgePairToEdgeProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_epp, gsi::arg ("processed"), + "@brief Applies a generic edge-pair-to-polygon processor and returns an Region with the results\n" + "See \\EdgePairToPolygonProcessor 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 " diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index f74687b1b..8b1041622 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -37,6 +37,196 @@ namespace gsi { +// --------------------------------------------------------------------------------- +// EdgeFilter binding + +class EdgeFilterImpl + : public shape_filter_impl +{ +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::issue_selected, edge); + } else { + return issue_selected (edge); + } + } + + // Returns true if all edges match the criterion + virtual bool selected (const std::unordered_set &edges) const + { + for (std::unordered_set::const_iterator e = edges.begin (); e != edges.end (); ++e) { + if (! selected (*e)) { + return false; + } + } + return true; + } + + gsi::Callback f_selected; + +private: + // No copying + EdgeFilterImpl &operator= (const EdgeFilterImpl &); + EdgeFilterImpl (const EdgeFilterImpl &); +}; + +Class 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 the \\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" +); + +// --------------------------------------------------------------------------------- +// EdgeProcessor binding + +Class > decl_EdgeProcessorBase ("db", "EdgeOperator", + shape_processor_impl::method_decls (true), + "@brief A generic edge-to-polygon operator\n" + "\n" + "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\Edges#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edges derived from the input edge.\n" + "The output edge collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 shrinks every edge to half of the size, but does not change the position.\n" + "In this example the 'position' is defined by the center of the edge:" + "\n" + "@code\n" + "class ShrinkToHalf < RBA::EdgeOperator\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # scale or orientation do not matter\n" + " end\n" + " \n" + " # Shrink to half size\n" + " def process(edge)\n" + " shift = edge.bbox.center - RBA::Point::new # shift vector\n" + " return [ (edge.moved(-shift) * 0.5).moved(shift) ]\n" + " end\n" + "\n" + "end\n" + "\n" + "edges = ... # some Edges collection\n" + "shrinked_to_half = edges.processed(ShrinkToHalf::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgeToPolygonProcessor ("db", "EdgeToPolygonOperator", + shape_processor_impl::method_decls (true), + "@brief A generic edge-to-polygon operator\n" + "\n" + "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\Edges#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input edge.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeOperator class, with the exception that this incarnation has to deliver edges.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_EdgeToEdgePairProcessor ("db", "EdgeToEdgePairOperator", + shape_processor_impl::method_decls (true), + "@brief A generic edge-to-edge-pair operator\n" + "\n" + "Edge processors are an efficient way to process edges from an edge collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\Edges#processed method.\n" + "\n" + "Conceptually, these methods take each edge from the edge collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edge pairs derived from the input edge.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode edge collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\EdgeOperator class, with the exception that this incarnation has to deliver edge pairs.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +// --------------------------------------------------------------------------------- +// Edges binding + static inline std::vector as_2edges_vector (const std::pair &rp) { std::vector res; @@ -204,6 +394,38 @@ 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 processed_ee (const db::Edges *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_ee (db::Edges *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::EdgePairs processed_eep (const db::Edges *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static db::Region processed_ep (const db::Edges *r, const shape_processor_impl *f) +{ + db::Region out; + r->processed (out, *f); + return out; +} + static db::Edges with_length1 (const db::Edges *r, db::Edges::distance_type length, bool inverse) { db::EdgeLengthFilter f (length, length + 1, inverse); @@ -627,6 +849,42 @@ Class 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 ("process", &process_ee, gsi::arg ("process"), + "@brief Applies a generic edge processor in place (replacing the edges from the Edges collection)\n" + "See \\EdgeProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_ee, gsi::arg ("processed"), + "@brief Applies a generic edge processor and returns a processed copy\n" + "See \\EdgeProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_eep, gsi::arg ("processed"), + "@brief Applies a generic edge-to-edge-pair processor and returns an edge pair collection with the results\n" + "See \\EdgeToEdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_ep, gsi::arg ("processed"), + "@brief Applies a generic edge-to-polygon processor and returns an edge collection with the results\n" + "See \\EdgeToPolygonProcessor 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 " diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index 417351c16..0c3960bb7 100644 --- a/src/db/db/gsiDeclDbPolygon.cc +++ b/src/db/db/gsiDeclDbPolygon.cc @@ -61,7 +61,7 @@ struct simple_polygon_defs } } - static point_type point (C *c, size_t p) + static point_type point (const C *c, size_t p) { if (c->hull ().size () > p) { return c->hull ()[p]; @@ -70,12 +70,12 @@ struct simple_polygon_defs } } - static size_t num_points (C *c) + static size_t num_points (const C *c) { return c->hull ().size (); } - static bool is_empty (C *c) + static bool is_empty (const C *c) { return c->hull ().size () == 0; } @@ -868,17 +868,17 @@ struct polygon_defs } } - static size_t num_points (C *c) + static size_t num_points (const C *c) { return c->vertices (); } - static bool is_empty (C *c) + static bool is_empty (const C *c) { return c->vertices () == 0; } - static point_type point_hull (C *c, size_t p) + static point_type point_hull (const C *c, size_t p) { if (c->hull ().size () > p) { return c->hull ()[p]; @@ -887,7 +887,7 @@ struct polygon_defs } } - static point_type point_hole (C *c, unsigned int n, size_t p) + static point_type point_hole (const C *c, unsigned int n, size_t p) { if (c->holes () > n && c->contour (n + 1).size () > p) { return c->contour (n + 1)[p]; @@ -896,12 +896,12 @@ struct polygon_defs } } - static size_t num_points_hull (C *c) + static size_t num_points_hull (const C *c) { return c->hull ().size (); } - static size_t num_points_hole (C *c, unsigned int n) + static size_t num_points_hole (const C *c, unsigned int n) { return c->contour (n + 1).size (); } diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 7bac67ea8..6135f6e9f 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -47,6 +47,190 @@ namespace gsi { +// --------------------------------------------------------------------------------- +// PolygonFilter binding + +class PolygonFilterImpl + : public shape_filter_impl +{ +public: + PolygonFilterImpl () { } + + bool issue_selected (const db::Polygon &) const + { + return false; + } + + virtual bool selected (const db::Polygon &polygon) const + { + if (f_selected.can_issue ()) { + return f_selected.issue (&PolygonFilterImpl::issue_selected, polygon); + } else { + return issue_selected (polygon); + } + } + + virtual bool selected (const db::PolygonRef &polygon) const + { + db::Polygon p; + polygon.instantiate (p); + return selected (p); + } + + gsi::Callback f_selected; + +private: + // No copying + PolygonFilterImpl &operator= (const PolygonFilterImpl &); + PolygonFilterImpl (const PolygonFilterImpl &); +}; + +Class decl_PolygonFilterImpl ("db", "PolygonFilter", + PolygonFilterImpl::method_decls (true) + + callback ("selected", &PolygonFilterImpl::issue_selected, &PolygonFilterImpl::f_selected, gsi::arg ("polygon"), + "@brief Selects a polygon\n" + "This method is the actual payload. It needs to be reimplemented in a derived class.\n" + "It needs to analyze the polygon and return 'true' if it should be kept and 'false' if it should be discarded." + ), + "@brief A generic polygon filter adaptor\n" + "\n" + "Polygon filters are an efficient way to filter polygons from a Region. To apply a filter, derive your own " + "filter class and pass an instance to the \\Region#filter or \\Region#filtered method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the filter's 'selected' method.\n" + "Based on the result of this evaluation, the polygon is kept or discarded.\n" + "\n" + "The magic happens when deep mode regions 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 triangles:" + "\n" + "@code\n" + "class TriangleFilter < RBA::PolygonFilter\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # the triangle nature is not dependent on the scale or orientation\n" + " end\n" + " \n" + " # Select only triangles\n" + " def selected(polygon)\n" + " return polygon.holes == 0 && polygon.num_points == 3\n" + " end\n" + "\n" + "end\n" + "\n" + "region = ... # some Region\n" + "triangles_only = region.filtered(TriangleFilter::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +// --------------------------------------------------------------------------------- +// PolygonProcessor binding + +Class > decl_PolygonOperator ("db", "PolygonOperator", + shape_processor_impl::method_decls (true), + "@brief A generic polygon operator\n" + "\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " + "operator class and pass an instance to the \\Region#process or \\Region#processed method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the operators' 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input polygon.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 shrinks every polygon to half of the size but does not change the position.\n" + "In this example the 'position' is defined by the center of the bounding box:" + "\n" + "@code\n" + "class ShrinkToHalf < RBA::PolygonOperator\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # scale or orientation do not matter\n" + " end\n" + " \n" + " # Shrink to half size\n" + " def process(polygon)\n" + " shift = polygon.bbox.center - RBA::Point::new # shift vector\n" + " return [ (polygon.moved(-shift) * 0.5).moved(shift) ]\n" + " end\n" + "\n" + "end\n" + "\n" + "region = ... # some Region\n" + "shrinked_to_half = region.processed(ShrinkToHalf::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_PolygonToEdgeProcessor ("db", "PolygonToEdgeOperator", + shape_processor_impl::method_decls (true), + "@brief A generic polygon-to-edge operator\n" + "\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " + "operator class and pass an instance to the \\Region#processed method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edges derived from the input polygon.\n" + "The output edge collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\PolygonOperator class, with the exception that this incarnation has to deliver edges.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_PolygonToEdgePairProcessor ("db", "PolygonToEdgePairOperator", + shape_processor_impl::method_decls (true), + "@brief A generic polygon-to-edge-pair operator\n" + "\n" + "Polygon processors are an efficient way to process polygons from a Region. To apply a processor, derive your own " + "operator class and pass an instance to the \\Region#processed method.\n" + "\n" + "Conceptually, these methods take each polygon from the region and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output edge pairs derived from the input polygon.\n" + "The output edge pair collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode regions are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\PolygonOperator class, with the exception that this incarnation has to deliver edge pairs.\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +// --------------------------------------------------------------------------------- +// Region binding + static inline std::vector as_2region_vector (const std::pair &rp) { std::vector res; @@ -318,6 +502,36 @@ static db::Edges extent_refs_edges (const db::Region *r, double fx1, double fy1, return r->processed (db::RelativeExtentsAsEdges (fx1, fy1, fx2, fy2)); } +static db::Region filtered (const db::Region *r, const PolygonFilterImpl *f) +{ + return r->filtered (*f); +} + +static void filter (db::Region *r, const PolygonFilterImpl *f) +{ + r->filter (*f); +} + +static db::Region processed_pp (const db::Region *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_pp (db::Region *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::EdgePairs processed_pep (const db::Region *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static db::Edges processed_pe (const db::Region *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + static db::Region with_perimeter1 (const db::Region *r, db::Region::perimeter_type perimeter, bool inverse) { db::RegionPerimeterFilter f (perimeter, perimeter + 1, inverse); @@ -2363,6 +2577,42 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This method has been introduced in version 0.28.\n" ) + + method_ext ("filter", &filter, gsi::arg ("filter"), + "@brief Applies a generic filter in place (replacing the polygons from the Region)\n" + "See \\PolygonFilter 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 \\PolygonFilter for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("process", &process_pp, gsi::arg ("process"), + "@brief Applies a generic polygon processor in place (replacing the polygons from the Region)\n" + "See \\PolygonProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_pp, gsi::arg ("processed"), + "@brief Applies a generic polygon processor and returns a processed copy\n" + "See \\PolygonProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_pep, gsi::arg ("processed"), + "@brief Applies a generic polygon-to-edge-pair processor and returns an edge pair collection with the results\n" + "See \\PolygonToEdgePairProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_pe, gsi::arg ("processed"), + "@brief Applies a generic polygon-to-edge processor and returns an edge collection with the results\n" + "See \\PolygonToEdgeProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + method_ext ("rectangles", &rectangles, "@brief Returns all polygons which are rectangles\n" "This method returns all polygons in self which are rectangles." diff --git a/src/db/db/gsiDeclDbText.cc b/src/db/db/gsiDeclDbText.cc index a823cecc8..be37cf36e 100644 --- a/src/db/db/gsiDeclDbText.cc +++ b/src/db/db/gsiDeclDbText.cc @@ -98,17 +98,17 @@ struct text_defs t->font (db::Font (f)); } - static int get_font (C *t) + static int get_font (const C *t) { return t->font (); } - static point_type get_pos (C *t) + static point_type get_pos (const C *t) { return t->trans () * point_type (); } - static box_type get_bbox (C *t) + static box_type get_bbox (const C *t) { point_type p = get_pos (t); return box_type (p, p); @@ -124,7 +124,7 @@ struct text_defs t->halign (db::HAlign (f)); } - static db::HAlign get_halign (C *t) + static db::HAlign get_halign (const C *t) { return t->halign (); } @@ -139,12 +139,12 @@ struct text_defs t->valign (db::VAlign (f)); } - static db::VAlign get_valign (C *t) + static db::VAlign get_valign (const C *t) { return t->valign (); } - static C moved (C *c, const vector_type &p) + static C moved (const C *c, const vector_type &p) { return c->transformed (simple_trans_type (p)); } @@ -155,7 +155,7 @@ struct text_defs return *c; } - static C moved_xy (C *c, coord_type dx, coord_type dy) + static C moved_xy (const C *c, coord_type dx, coord_type dy) { return c->transformed (simple_trans_type (vector_type (dx, dy))); } diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index b105c5d82..8be9238b8 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -33,6 +33,160 @@ namespace gsi { +// --------------------------------------------------------------------------------- +// TextFilter binding + +class TextFilterImpl + : public shape_filter_impl +{ +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::issue_selected, text); + } else { + return issue_selected (text); + } + } + + gsi::Callback f_selected; + +private: + // No copying + TextFilterImpl &operator= (const TextFilterImpl &); + TextFilterImpl (const TextFilterImpl &); +}; + +Class 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" +); + +// --------------------------------------------------------------------------------- +// TextProcessor binding + +Class > decl_TextProcessor ("db", "TextOperator", + shape_processor_impl::method_decls (false), + "@brief A generic text operator\n" + "\n" + "Text processors are an efficient way to process texts from an text collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\Texts#processed or \\Texts#process method.\n" + "\n" + "Conceptually, these methods take each text from the edge pair collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output texts derived from the input text.\n" + "The output text collection is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode text collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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 replaces the text string:" + "\n" + "@code\n" + "class ReplaceTextString < RBA::TextOperator\n" + "\n" + " # Constructor\n" + " def initialize\n" + " self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n" + " end\n" + " \n" + " # Replaces the string by a number representing the string length\n" + " def process(text)\n" + " new_text = text.dup # need a copy as we cannot modify the text passed\n" + " new_text.string = text.string.size.to_s\n" + " return [ new_text ]\n" + " end\n" + "\n" + "end\n" + "\n" + "texts = ... # some Texts object\n" + "modified = texts.processed(ReplaceTextString::new)\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.29.\n" +); + +Class > decl_TextToPolygonProcessor ("db", "TextToPolygonOperator", + shape_processor_impl::method_decls (false), + "@brief A generic text-to-polygon operator\n" + "\n" + "Text processors are an efficient way to process texts from an text collection. To apply a processor, derive your own " + "operator class and pass an instance to the \\Texts#processed method.\n" + "\n" + "Conceptually, these methods take each text from the text collection and present it to the operator's 'process' method.\n" + "The result of this call is a list of zero to many output polygons derived from the input text.\n" + "The output region is the sum over all these individual results.\n" + "\n" + "The magic happens when deep mode text collections are involved. In that case, the processor will use as few calls as possible " + "and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You " + "need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant " + "before using it.\n" + "\n" + "You can skip this step, but the processor 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" + "For a basic example see the \\TextOperator class, with the exception that this incarnation delivers polygons.\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 +306,33 @@ 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 processed_tt (const db::Texts *r, const shape_processor_impl *f) +{ + return r->processed (*f); +} + +static void process_tt (db::Texts *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::Region processed_tp (const db::Texts *r, const shape_processor_impl *f) +{ + db::Region out; + r->processed (out, *f); + return out; +} + static db::Texts with_text (const db::Texts *r, const std::string &text, bool inverse) { db::TextStringFilter f (text, inverse); @@ -401,6 +582,36 @@ Class 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 ("process", &process_tt, gsi::arg ("process"), + "@brief Applies a generic text processor in place (replacing the texts from the text collection)\n" + "See \\TextProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_tt, gsi::arg ("processed"), + "@brief Applies a generic text processor and returns a processed copy\n" + "See \\TextProcessor for a description of this feature.\n" + "\n" + "This method has been introduced in version 0.29.\n" + ) + + method_ext ("processed", &processed_tp, gsi::arg ("processed"), + "@brief Applies a generic text-to-polygon processor and returns a region with the results\n" + "See \\TextToPolygonProcessor 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" diff --git a/testdata/ruby/dbEdgePairsTest.rb b/testdata/ruby/dbEdgePairsTest.rb index fa1ccad3c..f9b3289be 100644 --- a/testdata/ruby/dbEdgePairsTest.rb +++ b/testdata/ruby/dbEdgePairsTest.rb @@ -30,6 +30,60 @@ 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 FlipEdgePair < RBA::EdgePairOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + end + + # Flips the edge pair + def process(edge_pair) + return [ RBA::EdgePair::new(edge_pair.second, edge_pair.first) ] + end + +end + +class SomeEdgePairToEdgeOperator < RBA::EdgePairToEdgeOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(ep) + return [ RBA::Edge::new(ep.first.p1, ep.second.p2) ] + end + +end + +class SomeEdgePairToPolygonOperator < RBA::EdgePairToPolygonOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(ep) + return [ RBA::Polygon::new(ep.bbox) ] + end + +end + class DBEdgePairs_TestClass < TestBase # Basics @@ -331,6 +385,90 @@ 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 + + # Generic processors + def test_generic_processors_epep + + # Some basic tests for the processor class + + f = FlipEdgePair::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 + + edge_pairs = RBA::EdgePairs::new + + edge_pairs.insert(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 100), RBA::Edge::new(200, 300, 200, 500))) + + assert_equal(edge_pairs.processed(FlipEdgePair::new).to_s, "(200,300;200,500)/(0,0;100,100)") + assert_equal(edge_pairs.to_s, "(0,0;100,100)/(200,300;200,500)") + edge_pairs.process(FlipEdgePair::new) + assert_equal(edge_pairs.to_s, "(200,300;200,500)/(0,0;100,100)") + + end + + # Generic processors + def test_generic_processors_epe + + p = SomeEdgePairToEdgeOperator::new + + edge_pairs = RBA::EdgePairs::new + + edge_pairs.insert(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 100), RBA::Edge::new(200, 300, 200, 500))) + + assert_equal(edge_pairs.processed(p).to_s, "(0,0;200,500)") + assert_equal(edge_pairs.to_s, "(0,0;100,100)/(200,300;200,500)") + + end + + # Generic processors + def test_generic_processors_epp + + p = SomeEdgePairToPolygonOperator::new + + edge_pairs = RBA::EdgePairs::new + + edge_pairs.insert(RBA::EdgePair::new(RBA::Edge::new(0, 0, 100, 100), RBA::Edge::new(200, 300, 200, 500))) + + assert_equal(edge_pairs.processed(p).to_s, "(0,0;0,500;200,500;200,0)") + assert_equal(edge_pairs.to_s, "(0,0;100,100)/(200,300;200,500)") + + end + end diff --git a/testdata/ruby/dbEdgesTest.rb b/testdata/ruby/dbEdgesTest.rb index f64dc910e..dc4ae88bb 100644 --- a/testdata/ruby/dbEdgesTest.rb +++ b/testdata/ruby/dbEdgesTest.rb @@ -30,6 +30,64 @@ 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 ShrinkToHalfEdgeOperator < RBA::EdgeOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + # Shrink to half size + def process(edge) + shift = edge.bbox.center - RBA::Point::new # shift vector + return [ (edge.moved(-shift) * 0.5).moved(shift) ] + end + +end + +class SomeEdgeToEdgePairOperator < RBA::EdgeToEdgePairOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(edge) + box = edge.bbox + return [ RBA::EdgePair::new([ box.left, box.bottom, box.left, box.top ], [ box.right, box.bottom, box.right, box.top ]) ] + end + +end + +class SomeEdgeToPolygonOperator < RBA::EdgeToPolygonOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(edge) + box = edge.bbox + return [ RBA::Polygon::new(box) ] + end + +end + class DBEdges_TestClass < TestBase # Basics @@ -791,6 +849,105 @@ 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 + + # Generic processors + def test_generic_processors_ee + + # Some basic tests for the processor class + + f = ShrinkToHalfEdgeOperator::new + 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 = true + assert_equal(f.requires_raw_input, true) + assert_equal(f.result_is_merged, false) + f.result_is_merged = true + assert_equal(f.result_is_merged, true) + assert_equal(f.result_must_not_be_merged, false) + f.result_must_not_be_merged = true + assert_equal(f.result_must_not_be_merged, true) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + edges = RBA::Edges::new + + edges.insert(RBA::Edge::new(0, 0, 100, 100)) + edges.insert(RBA::Edge::new(200, 300, 200, 500)) + + assert_equal(edges.processed(ShrinkToHalfEdgeOperator::new).to_s, "(25,25;75,75);(200,350;200,450)") + assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)") + edges.process(ShrinkToHalfEdgeOperator::new) + assert_equal(edges.to_s, "(25,25;75,75);(200,350;200,450)") + + end + + # Generic processors + def test_generic_processors_eep + + p = SomeEdgeToEdgePairOperator::new + + edges = RBA::Edges::new + + edges.insert(RBA::Edge::new(0, 0, 100, 100)) + edges.insert(RBA::Edge::new(200, 300, 200, 500)) + + assert_equal(edges.processed(p).to_s, "(0,0;0,100)/(100,0;100,100);(200,300;200,500)/(200,300;200,500)") + assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)") + + end + + # Generic processors + def test_generic_processors_ep + + p = SomeEdgeToPolygonOperator::new + + edges = RBA::Edges::new + + edges.insert(RBA::Edge::new(0, 0, 100, 100)) + edges.insert(RBA::Edge::new(200, 300, 200, 500)) + + assert_equal(edges.processed(p).to_s, "(0,0;0,100;100,100;100,0);(200,300;200,300;200,500;200,500)") + assert_equal(edges.to_s, "(0,0;100,100);(200,300;200,500)") + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index d08f161fa..0d81b5efd 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -30,6 +30,63 @@ def csort(s) s.split(/(?<=\));(?=\()/).sort.join(";") end +class TriangleFilter < RBA::PolygonFilter + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # the triangle nature is not dependent on the scale or orientation + end + + # Select only triangles + def selected(polygon) + return polygon.holes == 0 && polygon.num_points == 3 + end + +end + +class ShrinkToHalfOperator < RBA::PolygonOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + # Shrink to half size + def process(polygon) + shift = polygon.bbox.center - RBA::Point::new # shift vector + return [ (polygon.moved(-shift) * 0.5).moved(shift) ] + end + +end + +class SomePolygonToEdgePairOperator < RBA::PolygonToEdgePairOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(polygon) + box = polygon.bbox + return [ RBA::EdgePair::new([ box.left, box.bottom, box.left, box.top ], [ box.right, box.bottom, box.right, box.top ]) ] + end + +end + +class SomePolygonToEdgeOperator < RBA::PolygonToEdgeOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # scale or orientation do not matter + end + + def process(polygon) + box = polygon.bbox + return [ RBA::Edge::new(box.p1, box.p2) ] + end + +end + class DBRegion_TestClass < TestBase # Basics @@ -1226,6 +1283,104 @@ class DBRegion_TestClass < TestBase end + # Generic filters + def test_generic_filters + + # Some basic tests for the filter class + + f = TriangleFilter::new + 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 = true + assert_equal(f.requires_raw_input, true) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + region = RBA::Region::new + + region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]])) + 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 + + # Generic processors + def test_generic_processors_pp + + # Some basic tests for the processor class + + f = ShrinkToHalfOperator::new + 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 = true + assert_equal(f.requires_raw_input, true) + assert_equal(f.result_is_merged, false) + f.result_is_merged = true + assert_equal(f.result_is_merged, true) + assert_equal(f.result_must_not_be_merged, false) + f.result_must_not_be_merged = true + assert_equal(f.result_must_not_be_merged, true) + + # Smoke test + f.is_isotropic + f.is_scale_invariant + + # Some application + + region = RBA::Region::new + + region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]])) + region.insert(RBA::Box::new(200, 0, 300, 100)) + + assert_equal(region.processed(ShrinkToHalfOperator::new).to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") + assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") + region.process(ShrinkToHalfOperator::new) + assert_equal(region.to_s, "(25,25;75,75;75,25);(225,25;225,75;275,75;275,25)") + + end + + # Generic processors + def test_generic_processors_pep + + p = SomePolygonToEdgePairOperator::new + + region = RBA::Region::new + + region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]])) + region.insert(RBA::Box::new(200, 0, 300, 100)) + + assert_equal(region.processed(p).to_s, "(0,0;0,100)/(100,0;100,100);(200,0;200,100)/(300,0;300,100)") + assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") + + end + + # Generic processors + def test_generic_processors_pe + + p = SomePolygonToEdgeOperator::new + + region = RBA::Region::new + + region.insert(RBA::Polygon::new([[0,0], [100, 100], [100,0]])) + region.insert(RBA::Box::new(200, 0, 300, 100)) + + assert_equal(region.processed(p).to_s, "(0,0;100,100);(200,0;300,100)") + assert_equal(region.to_s, "(0,0;100,100;100,0);(200,0;200,100;300,100;300,0)") + + end + # rasterize def test_rasterize diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 7b9a65288..70429a1a0 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -29,6 +29,52 @@ def csort(s) # splits at ");(" without consuming the brackets 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 ReplaceTextString < RBA::TextOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + end + + # Replaces the string by a number representing the string length + def process(text) + new_text = text.dup # need a copy as we cannot modify the text passed + new_text.string = text.string.size.to_s + return [ new_text ] + end + +end + +class SomeTextToPolygonOperator < RBA::TextToPolygonOperator + + # Constructor + def initialize + self.is_isotropic_and_scale_invariant # orientation and scale do not matter + end + + # Replaces the string by a number representing the string length + def process(text) + s = text.string.size * 10 + return [ RBA::Polygon::new(text.bbox.enlarged(s)) ] + end + +end class DBTexts_TestClass < TestBase @@ -324,6 +370,79 @@ 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 + + # Generic processors + def test_generic_processors_tt + + # Some basic tests for the processor class + + f = ReplaceTextString::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 + + texts = RBA::Texts::new + + texts.insert(RBA::Text::new("abc", RBA::Trans::new)) + texts.insert(RBA::Text::new("a long text", RBA::Trans::M45)) + + assert_equal(texts.processed(ReplaceTextString::new).to_s, "('3',r0 0,0);('11',m45 0,0)") + assert_equal(texts.to_s, "('abc',r0 0,0);('a long text',m45 0,0)") + texts.process(ReplaceTextString::new) + assert_equal(texts.to_s, "('3',r0 0,0);('11',m45 0,0)") + + end + + # Generic processors + def test_generic_processors_tp + + p = SomeTextToPolygonOperator::new + + texts = RBA::Texts::new + + texts.insert(RBA::Text::new("abc", RBA::Trans::new)) + texts.insert(RBA::Text::new("a long text", RBA::Trans::M45)) + + assert_equal(texts.processed(p).to_s, "(-30,-30;-30,30;30,30;30,-30);(-110,-110;-110,110;110,110;110,-110)") + assert_equal(texts.to_s, "('abc',r0 0,0);('a long text',m45 0,0)") + + end + end