diff --git a/src/db/db/dbMeasure.cc b/src/db/db/dbMeasure.cc index 9f8174616..f5cd594f8 100644 --- a/src/db/db/dbMeasure.cc +++ b/src/db/db/dbMeasure.cc @@ -26,20 +26,531 @@ #include "dbEdgePairs.h" #include "dbTexts.h" #include "tlExpression.h" +#include "gsiClassBase.h" +#include "gsiDeclDbContainerHelpers.h" namespace db { +// ------------------------------------------------------------------------------------- +// Some utilities +namespace +{ -template -void compute_as_properties (Container *container, const std::map &expressions, bool clear_properties) +/** + * @brief A class collecting the properties names from the shapes delivered by a RecursiveShapeIterator + * + * This class implements the "RecursiveShapeReceiver" interface and will collect all property names + * present in the shapes delivered by a RecursiveShapeIterator. Use this class as a target for + * the RecursiveShapeIterator's "push" method. After this, "names" will give you a set with the + * property names found. + */ +class PropertyNamesCollector + : public db::RecursiveShapeReceiver +{ +public: + PropertyNamesCollector () + : m_names (), m_name_ids () + { + // .. nothing yet .. + } + + const std::set &names () const + { + return m_names; + } + + virtual void enter_cell (const db::RecursiveShapeIterator * /*iter*/, const db::Cell *cell, const db::Box & /*region*/, const box_tree_type * /*complex_region*/) + { + m_cell_ids.insert (cell->cell_index ()); + } + + virtual new_inst_mode new_inst (const db::RecursiveShapeIterator * /*iter*/, const db::CellInstArray &inst, const db::ICplxTrans & /*always_apply*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/, bool /*all*/, bool /*skip_shapes*/) + { + if (m_cell_ids.find (inst.object ().cell_index ()) != m_cell_ids.end ()) { + return NI_skip; + } else { + return NI_single; + } + } + + virtual void shape (const db::RecursiveShapeIterator * /*iter*/, const db::Shape &shape, const db::ICplxTrans & /*always_apply*/, const db::ICplxTrans & /*trans*/, const db::Box & /*region*/, const box_tree_type * /*complex_region*/) + { + auto pid = shape.prop_id (); + if (pid != 0 && m_pids.find (pid) == m_pids.end ()) { + m_pids.insert (pid); + const db::PropertiesSet &ps = db::properties (pid); + for (auto i = ps.begin (); i != ps.end (); ++i) { + if (m_name_ids.find (i->first) == m_name_ids.end ()) { + m_name_ids.insert (i->first); + m_names.insert (db::property_name (i->first)); + } + } + } + } + +private: + std::set m_names; + std::unordered_set m_name_ids; + std::unordered_set m_pids; + std::set m_cell_ids; +}; + +/** + * @brief An evaluation context for the expressions + * + * This class provides the methods, functions and variables for the expressions. + */ +class MeasureEval + : public tl::Eval +{ +public: + MeasureEval () + : m_shape_type (None), m_prop_id (0) + { + mp_shape.any = 0; + } + + void init (const std::set &names) + { + define_function ("shape", new ShapesFunction (this)); + define_function ("value", new ValueFunction (this)); + define_function ("values", new ValuesFunction (this)); + + for (auto n = names.begin (); n != names.end (); ++n) { + if (n->is_a_string ()) { + // TODO: should check, if the name is a word + define_function (n->to_string (), new PropertyFunction (this, n->to_string ())); + } + } + } + + void reset_shape () const + { + m_shape_type = None; + mp_shape.any = 0; + m_prop_id = 0; + } + + void set_shape (const db::Polygon *poly) const + { + m_shape_type = Polygon; + mp_shape.poly = poly; + } + + void set_shape (const db::PolygonRef *poly) const + { + m_shape_type = PolygonRef; + mp_shape.poly_ref = poly; + } + + void set_shape (const db::Edge *edge) const + { + m_shape_type = Edge; + mp_shape.edge = edge; + } + + void set_shape (const db::EdgePair *edge_pair) const + { + m_shape_type = EdgePair; + mp_shape.edge_pair = edge_pair; + } + + void set_shape (const db::Text *text) const + { + m_shape_type = Text; + mp_shape.text = text; + } + + void set_prop_id (db::properties_id_type prop_id) const + { + m_prop_id = prop_id; + } + +private: + class ShapesFunction + : public tl::EvalFunction + { + public: + ShapesFunction (MeasureEval *eval) + : mp_eval (eval) + { + // .. nothing yet .. + } + + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const + { + if (args.size () != 0) { + throw tl::EvalError (tl::to_string (tr ("'shape' function does not take arguments")), context); + } + out = mp_eval->shape_func (); + } + + private: + MeasureEval *mp_eval; + }; + + class ValueFunction + : public tl::EvalFunction + { + public: + ValueFunction (MeasureEval *eval) + : mp_eval (eval) + { + // .. nothing yet .. + } + + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const + { + if (args.size () != 1) { + throw tl::EvalError (tl::to_string (tr ("'value' function takes one argument")), context); + } + out = mp_eval->value_func (args [0]); + } + + private: + MeasureEval *mp_eval; + }; + + class ValuesFunction + : public tl::EvalFunction + { + public: + ValuesFunction (MeasureEval *eval) + : mp_eval (eval) + { + // .. nothing yet .. + } + + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const + { + if (args.size () != 1) { + throw tl::EvalError (tl::to_string (tr ("'values' function takes one argument")), context); + } + out = mp_eval->values_func (args [0]); + } + + private: + MeasureEval *mp_eval; + }; + + class PropertyFunction + : public tl::EvalFunction + { + public: + PropertyFunction (MeasureEval *eval, const tl::Variant &name) + : mp_eval (eval), m_name (name) + { + // .. nothing yet .. + } + + virtual void execute (const tl::ExpressionParserContext &context, tl::Variant &out, const std::vector &args, const std::map * /*kwargs*/) const + { + if (args.size () != 0) { + throw tl::EvalError (tl::to_string (tr ("Property getter function does not take arguments")), context); + } + out = mp_eval->value_func (m_name); + } + + private: + MeasureEval *mp_eval; + tl::Variant m_name; + }; + + union ShapeRef + { + const db::Polygon *poly; + const db::PolygonRef *poly_ref; + const db::Edge *edge; + const db::EdgePair *edge_pair; + const db::Text *text; + void *any; + }; + + enum ShapeType + { + None, + Polygon, + PolygonRef, + Edge, + EdgePair, + Text + }; + + mutable ShapeType m_shape_type; + mutable ShapeRef mp_shape; + mutable db::properties_id_type m_prop_id; + + tl::Variant shape_func () const + { + switch (m_shape_type) + { + case None: + default: + return tl::Variant (); + case Polygon: + return tl::Variant (mp_shape.poly); + case PolygonRef: + return tl::Variant (mp_shape.poly_ref); + case Edge: + return tl::Variant (mp_shape.edge); + case EdgePair: + return tl::Variant (mp_shape.edge_pair); + case Text: + return tl::Variant (mp_shape.text); + } + } + + tl::Variant value_func (const tl::Variant &name) const + { + const db::PropertiesSet &ps = db::properties (m_prop_id); + for (auto i = ps.begin (); i != ps.end (); ++i) { + if (db::property_name (i->first) == name) { + return db::property_value (i->second); + } + } + + return tl::Variant (); + } + + tl::Variant values_func (const tl::Variant &name) const + { + tl::Variant res = tl::Variant::empty_list (); + + const db::PropertiesSet &ps = db::properties (m_prop_id); + for (auto i = ps.begin (); i != ps.end (); ++i) { + if (db::property_name (i->first) == name) { + res.push (db::property_value (i->second)); + } + } + + return res; + } +}; + +static db::RecursiveShapeIterator +begin_iter (db::Region *region) +{ + return region->merged_semantics () ? region->begin_merged_iter ().first : region->begin_iter ().first; +} + +static bool +is_merged (db::Region *region) +{ + return region->merged_semantics (); +} + +static db::RecursiveShapeIterator +begin_iter (db::Edges *edges) +{ + return edges->merged_semantics () ? edges->begin_merged_iter ().first : edges->begin_iter ().first; +} + +static bool +is_merged (db::Edges *edges) +{ + return edges->merged_semantics (); +} + +static db::RecursiveShapeIterator +begin_iter (db::EdgePairs *edge_pairs) +{ + return edge_pairs->begin_iter ().first; +} + +static bool +is_merged (db::EdgePairs *) +{ + return false; +} + +static db::RecursiveShapeIterator +begin_iter (db::Texts *texts) +{ + return texts->begin_iter ().first; +} + +static bool +is_merged (db::Texts *) +{ + return false; +} + +/** + * @brief A specialization of the shape processor + * + * This class provides the evaluation of the expressions in the context of + * a specific shape and shape properties. It allows creating properties with + * a computed value. + */ +template +class property_computation_processor + : public gsi::shape_processor_base +{ +public: + typedef typename ProcessorBase::shape_type shape_type; + typedef typename ProcessorBase::result_type result_type; + + property_computation_processor (Container *container, const std::map &expressions, bool copy_properties) + : m_eval (), m_copy_properties (copy_properties) + { + PropertyNamesCollector names_collector; + if (container) { + + db::RecursiveShapeIterator iter = begin_iter (container); + iter.push (&names_collector); + + this->set_result_is_merged (is_merged (container)); + + } + + m_eval.init (names_collector.names ()); + + // compile the expressions + for (auto e = expressions.begin (); e != expressions.end (); ++e) { + m_expressions.push_back (std::make_pair (db::property_names_id (e->first), tl::Expression ())); + tl::Extractor ex (e->second.c_str ()); + m_eval.parse (m_expressions.back ().second, ex); + } + } + + virtual void process (const db::object_with_properties &shape, std::vector > &res) const + { + res.push_back (shape); + + m_eval.set_prop_id (shape.properties_id ()); + m_eval.set_shape (&shape); + + db::PropertiesSet ps; + if (m_copy_properties) { + ps = db::properties (shape.properties_id ()); + } + + for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) { + ps.insert (e->first, e->second.execute ()); + } + + res.back ().properties_id (db::properties_id (ps)); + } + +public: + MeasureEval m_eval; + std::vector > m_expressions; + bool m_copy_properties; +}; + +} + +// ------------------------------------------------------------------------------------- + +/** + * @brief Provides methods to handle measurement functions on various containers + */ +template +struct measure_methods +{ + /** + * @brief Computes one or many properties from expressions + * + * This method will use the shapes from the "input" container and compute properties from them using the + * given expression from "expressions". This map specifies the name of the target property, the value + * specifies the expression to execute. + * + * The expressions can make use of the following variables and functions: + * * "shape": the shape which is currently seen + * * "": an existing property from the shape currently seen (or nil, if no such property is present). + * This is a shortcut, only for properties with string names that are compatible with variable names + * * "value()": the value of the property with the given name - if multiple properties with that + * name are present, one value is returned + * * "values()": a list of values for all properties with the given name + * + * Returns the new container with the computed properties attached. + */ + Container computed_properties (Container *input, const std::map &expressions, bool clear_properties); + + /** + * @brief Computes one or many properties from expressions + * + * Like "computed_properties", this method computes properties, but attaches them to the existing shapes. + * As a side effect, the shapes may be merged if "merged_semantics" applies. If "clear_properties" is true, + * any existing properties will be removed. If not, the new properties are added to the existing ones. + */ + void compute_properties_in_place (Container *container, const std::map &expressions, bool clear_properties); + + /** + * @brief Selects all shapes for which the condition expression renders true (or the inverse) + * + * The condition expression can use the features as described for "computed_properties". + * If inverse is false, all shapes are selected for which the condition renders true. If + * inverse is true, all shapes are selected for which the condition renders false. + */ + Container selected_if (const Container &container, const std::string &condition_expression, bool inverse); + + /** + * @brief In-place version of "selected_if" + */ + void select_if (const Container &container, const std::string &condition_expression, bool inverse); + + /** + * @brief Splits the container into one for which is the condition is true and one with the other shapes + */ + std::pair split_if (const Container &container, const std::string &condition_expression); +}; + +template +Container +measure_methods::computed_properties (Container *container, const std::map &expressions, bool clear_properties) +{ + property_computation_processor proc (container, expressions, !clear_properties); + return container->processed (proc); +} + +template +void +measure_methods::compute_properties_in_place (Container *container, const std::map &expressions, bool clear_properties) +{ + property_computation_processor proc (container, expressions, !clear_properties); + container->process (proc); +} + +template +Container +measure_methods::selected_if (const Container &container, const std::string &condition_expression, bool inverse) { // - collect property names - // - define functions for - + // - define tl::Eval functions for property names (properties_id -> value) + // - define tl::Eval function for shape + // - compile expression + // - launch filter (Region::filtered) } +template +void +measure_methods::select_if (const Container &container, const std::string &condition_expression, bool inverse) +{ + // - collect property names + // - define tl::Eval functions for property names (properties_id -> value) + // - define tl::Eval function for shape + // - compile expression + // - launch filter (Region::filtered) +} +template +std::pair +measure_methods::split_if (const Container &container, const std::string &condition_expression) +{ + // - collect property names + // - define tl::Eval functions for property names (properties_id -> value) + // - define tl::Eval function for shape + // - compile expression + // - launch filter (Region::filtered) +} + +// explicit instantiations +template struct measure_methods >; +template struct measure_methods >; +template struct measure_methods >; +template struct measure_methods >; } diff --git a/src/db/db/gsiDeclDbContainerHelpers.h b/src/db/db/gsiDeclDbContainerHelpers.h index d52eef4b6..e347f4f38 100644 --- a/src/db/db/gsiDeclDbContainerHelpers.h +++ b/src/db/db/gsiDeclDbContainerHelpers.h @@ -237,15 +237,22 @@ private: // --------------------------------------------------------------------------------- // Generic shape processor declarations +/** + * @brief A "declarative" base for shape processors + * + * This implementation provides configuration options to tailor the behavior + * of the processor base - specifically with respect to variants and various + * attributes. + */ template -class shape_processor_impl +class shape_processor_base : public ProcessorBase { public: - typedef typename ProcessorBase::shape_type shape_type; - typedef typename ProcessorBase::result_type result_type; + typedef typename ProcessorBase::shape_type shape_type; + typedef typename ProcessorBase::result_type result_type; - shape_processor_impl () + shape_processor_base () { mp_vars = &m_mag_and_orient; m_wants_variants = true; @@ -319,79 +326,17 @@ public: mp_vars = 0; } - virtual void process (const shape_type &shape, std::vector &res) const - { - res = do_process (shape); - } - - virtual void process (const db::object_with_properties &shape, std::vector > &res) const - { - res = do_process (shape); - } - - std::vector issue_do_process (const shape_type &) const - { - return std::vector (); - } - - std::vector > issue_do_process_wp (const db::object_with_properties &) 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); - } - } - - std::vector > do_process (const db::object_with_properties &shape) const - { - if (f_process_wp.can_issue ()) { - return f_process.issue >, const db::object_with_properties &> (&shape_processor_impl::issue_do_process_wp, shape); - } else if (f_process.can_issue ()) { - auto tmp_result = f_process.issue, const shape_type &> (&shape_processor_impl::issue_do_process, shape); - std::vector > result; - for (auto i = tmp_result.begin (); i != tmp_result.end (); ++i) { - result.push_back (db::object_with_properties (*i, shape.properties_id ())); - } - return result; - } else { - return issue_do_process_wp (shape); - } - } - - gsi::Callback f_process; - gsi::Callback f_process_wp; - 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" - ) + - callback ("process_with_properties", &shape_processor_impl::issue_do_process_wp, &shape_processor_impl::f_process_wp, gsi::arg ("shape"), - "@brief Processes a shape with properties\n" - "In scenarios with shapes with properties, this method is called to process the shapes. If the method is not implemented, " - "the property-less 'process' method is called and the properties are copied from the input to the output.\n" - "\n" - "This flavor has been introduced in version 0.30." - ); + gsi::Methods decls; if (with_merged_options) { decls += - method ("requires_raw_input?", &shape_processor_impl::requires_raw_input, + method ("requires_raw_input?", &shape_processor_base::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"), + method ("requires_raw_input=", &shape_processor_base::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" @@ -401,22 +346,22 @@ public: "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, + method ("result_is_merged?", &shape_processor_base::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"), + method ("result_is_merged=", &shape_processor_base::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, + method ("result_must_not_be_merged?", &shape_processor_base::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"), + method ("result_must_not_be_merged=", &shape_processor_base::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." @@ -424,11 +369,11 @@ public: } decls += - method ("wants_variants?", &shape_processor_impl::wants_variants, + method ("wants_variants?", &shape_processor_base::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"), + method ("wants_variants=", &shape_processor_base::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) " @@ -440,7 +385,7 @@ public: "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, + method ("is_isotropic", &shape_processor_base::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 " @@ -449,7 +394,7 @@ public: "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, + method ("is_scale_invariant", &shape_processor_base::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 " @@ -458,7 +403,7 @@ public: "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, + method ("is_isotropic_and_scale_invariant", &shape_processor_base::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 " @@ -481,6 +426,71 @@ private: bool m_result_is_merged; bool m_result_must_not_be_merged; + // No copying + shape_processor_base &operator= (const shape_processor_base &); + shape_processor_base (const shape_processor_base &); +}; + +/** + * @brief The implementation class for the generic shape processor + * + * In addition to the services provided by "shape_processor_base", + * this class provides an overload slot for the "process" virtual method. + */ +template +class shape_processor_impl + : public shape_processor_base +{ +public: + typedef typename ProcessorBase::shape_type shape_type; + typedef typename ProcessorBase::result_type result_type; + + shape_processor_impl () + : shape_processor_base () + { + // .. nothing yet .. + } + + virtual void process (const db::object_with_properties &shape, std::vector > &res) const + { + res = do_process (shape); + } + + std::vector > issue_do_process (const db::object_with_properties &) const + { + return std::vector > (); + } + + std::vector > do_process (const db::object_with_properties &shape) const + { + if (f_process.can_issue ()) { + return f_process.issue >, const db::object_with_properties &> (&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" + "\n" + "Since version 0.30.3, this method will always receive objects with properties and is also able to deliver them - " + "hence modify the properties.\n" + "'process_with_properties' is no longer supported." + ); + + return decls + shape_processor_base::method_decls (with_merged_options); + } + +private: // No copying shape_processor_impl &operator= (const shape_processor_impl &); shape_processor_impl (const shape_processor_impl &); diff --git a/src/rdb/rdb/rdbUtils.cc b/src/rdb/rdb/rdbUtils.cc index 4692b35bd..247b73ebf 100644 --- a/src/rdb/rdb/rdbUtils.cc +++ b/src/rdb/rdb/rdbUtils.cc @@ -162,7 +162,7 @@ public: create_item_from_shape (mp_rdb, m_cell_stack.back ()->id (), mp_cat->id (), m_trans, shape, m_with_properties); } -public: +private: rdb::Category *mp_cat; rdb::Database *mp_rdb; std::vector m_cell_stack;