From c0bdc2874a1813cbe37b8dbf4f6dc47df8a5f12b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 29 Jul 2025 21:11:34 +0200 Subject: [PATCH] 'put' function is property computation processors --- src/db/db/gsiDeclDbEdgePairs.cc | 22 ++++++++++++++++ src/db/db/gsiDeclDbEdges.cc | 26 +++++++++++++++++-- src/db/db/gsiDeclDbMeasureHelpers.cc | 39 ++++++++++++++++++++++++++-- src/db/db/gsiDeclDbMeasureHelpers.h | 39 +++++++++++++++++++++------- src/db/db/gsiDeclDbRegion.cc | 27 ++++++++++++++++--- src/db/db/gsiDeclDbTexts.cc | 27 ++++++++++++++++--- testdata/ruby/dbEdgePairsTest.rb | 4 +++ testdata/ruby/dbEdgesTest.rb | 4 +++ testdata/ruby/dbRegionTest.rb | 4 +++ testdata/ruby/dbTextsTest.rb | 4 +++ 10 files changed, 177 insertions(+), 19 deletions(-) diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 743e3c4d6..7ece0e1e8 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -261,6 +261,15 @@ new_pcp (const db::EdgePairs *container, const std::map (container, expressions, copy_properties, dbu); } +static +property_computation_processor * +new_pcps (const db::EdgePairs *container, const std::string &expression, bool copy_properties, double dbu) +{ + std::map expressions; + expressions.insert (std::make_pair (tl::Variant (), expression)); + return new property_computation_processor (container, expressions, copy_properties, dbu); +} + Class > decl_EdgePairPropertiesExpressions (decl_EdgePairProcessorBase, "db", "EdgePairPropertiesExpressions", property_computation_processor::method_decls (true) + gsi::constructor ("new", &new_pcp, gsi::arg ("edge_pairs"), gsi::arg ("expressions"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), @@ -270,6 +279,14 @@ Class > "@param expressions A map of property names and expressions used to generate the values of the properties (see class description for details).\n" "@param copy_properties If true, new properties will be added to existing ones.\n" "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" + ) + + gsi::constructor ("new", &new_pcps, gsi::arg ("edge_pairs"), gsi::arg ("expression"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), + "@brief Creates a new properties expressions operator\n" + "\n" + "@param edge_pairs The edge pair collection, the processor will be used on. Can be nil, but if given, allows some optimization.\n" + "@param expression A single expression evaluated for each shape (see class description for details).\n" + "@param copy_properties If true, new properties will be added to existing ones.\n" + "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" ), "@brief An operator attaching computed properties to the edge pairs\n" "\n" @@ -278,10 +295,15 @@ Class > "\n" "A number of expressions can be supplied with a name. The expressions will be evaluated and the result " "is attached to the output edge pairs as user properties with the given names.\n" + "\n" + "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " + "to the output shape.\n" + "\n" "The expression may use the following variables and functions:\n" "\n" "@ul\n" "@li @b shape @/b: The current shape (i.e. 'EdgePair' without DBU specified or 'DEdgePair' otherwise) @/li\n" + "@li @b put(, ) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b value() @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b values() @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b @/b: A shortcut for 'value()' ( is used as a symbol) @/li\n" diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 036391851..2455b1abf 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -266,6 +266,15 @@ new_pcp (const db::Edges *container, const std::map &e return new property_computation_processor (container, expressions, copy_properties, dbu); } +static +property_computation_processor * +new_pcps (const db::Edges *container, const std::string &expression, bool copy_properties, double dbu) +{ + std::map expressions; + expressions.insert (std::make_pair (tl::Variant (), expression)); + return new property_computation_processor (container, expressions, copy_properties, dbu); +} + Class > decl_EdgePropertiesExpressions (decl_EdgeProcessorBase, "db", "EdgePropertiesExpressions", property_computation_processor::method_decls (true) + gsi::constructor ("new", &new_pcp, gsi::arg ("edges"), gsi::arg ("expressions"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), @@ -275,18 +284,31 @@ Class > decl_Ed "@param expressions A map of property names and expressions used to generate the values of the properties (see class description for details).\n" "@param copy_properties If true, new properties will be added to existing ones.\n" "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" + ) + + gsi::constructor ("new", &new_pcps, gsi::arg ("edges"), gsi::arg ("expression"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), + "@brief Creates a new properties expressions operator\n" + "\n" + "@param edges The edge collection, the processor will be used on. Can be nil, but if given, allows some optimization.\n" + "@param expression A single expression evaluated for each shape (see class description for details).\n" + "@param copy_properties If true, new properties will be added to existing ones.\n" + "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" ), - "@brief An operator attaching computed properties to the edges\n" + "@brief An operator attaching computed properties to the edge pairs\n" "\n" "This operator will execute a number of expressions and attach the results as new properties. " "The expression inputs can be taken either from the edges themselves or from existing properties.\n" "\n" "A number of expressions can be supplied with a name. The expressions will be evaluated and the result " - "is attached to the output edges as user properties with the given names.\n" + "is attached to the output edge pairs as user properties with the given names.\n" + "\n" + "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " + "to the output shape.\n" + "\n" "The expression may use the following variables and functions:\n" "\n" "@ul\n" "@li @b shape @/b: The current shape (i.e. 'Edge' without DBU specified or 'DEdge' otherwise) @/li\n" + "@li @b put(, ) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b value() @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b values() @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b @/b: A shortcut for 'value()' ( is used as a symbol) @/li\n" diff --git a/src/db/db/gsiDeclDbMeasureHelpers.cc b/src/db/db/gsiDeclDbMeasureHelpers.cc index 06998cc86..ad3958037 100644 --- a/src/db/db/gsiDeclDbMeasureHelpers.cc +++ b/src/db/db/gsiDeclDbMeasureHelpers.cc @@ -114,11 +114,34 @@ private: db::property_names_id_type m_name_id; }; +class PutFunction + : public tl::EvalFunction +{ +public: + PutFunction (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 () != 2) { + throw tl::EvalError (tl::to_string (tr ("'put' function takes two arguments (name, value)")), context); + } + mp_eval->put_func (args [0], args [1]); + } + +private: + MeasureEval *mp_eval; + db::property_names_id_type m_name_id; +}; + // -------------------------------------------------------------------- // MeasureEval implementation -MeasureEval::MeasureEval (double dbu) - : m_shape_type (None), m_prop_id (0), m_dbu (dbu) +MeasureEval::MeasureEval (double dbu, bool with_put) + : m_shape_type (None), m_prop_id (0), m_dbu (dbu), m_with_put (with_put) { mp_shape.any = 0; } @@ -126,6 +149,10 @@ MeasureEval::MeasureEval (double dbu) void MeasureEval::init () { + if (m_with_put) { + define_function ("put", new PutFunction (this)); + } + define_function ("shape", new ShapeFunction (this)); define_function ("value", new ValueFunction (this)); define_function ("values", new ValuesFunction (this)); @@ -288,4 +315,12 @@ MeasureEval::values_func (const tl::Variant &name) const return res; } +void +MeasureEval::put_func (const tl::Variant &name, const tl::Variant &value) const +{ + auto prop_name_id = db::property_names_id (name); + m_prop_set_out.erase (prop_name_id); + m_prop_set_out.insert (prop_name_id, value); +} + } diff --git a/src/db/db/gsiDeclDbMeasureHelpers.h b/src/db/db/gsiDeclDbMeasureHelpers.h index 5fd8f4363..6013aa65b 100644 --- a/src/db/db/gsiDeclDbMeasureHelpers.h +++ b/src/db/db/gsiDeclDbMeasureHelpers.h @@ -48,7 +48,7 @@ class DB_PUBLIC MeasureEval : public tl::Eval { public: - MeasureEval (double dbu); + MeasureEval (double dbu, bool with_put); void init (); @@ -60,6 +60,11 @@ public: void set_shape (const db::Text *text) const; void set_prop_id (db::properties_id_type prop_id) const; + db::PropertiesSet &prop_set_out () const + { + return m_prop_set_out; + } + protected: virtual void resolve_name (const std::string &name, const tl::EvalFunction *&function, const tl::Variant *&value, tl::Variant *&var); @@ -68,6 +73,7 @@ private: friend class ValueFunction; friend class ValuesFunction; friend class PropertyFunction; + friend class PutFunction; union ShapeRef { @@ -92,12 +98,15 @@ private: mutable ShapeType m_shape_type; mutable ShapeRef mp_shape; mutable db::properties_id_type m_prop_id; + mutable db::PropertiesSet m_prop_set_out; double m_dbu; + bool m_with_put; tl::Variant shape_func () const; tl::Variant value_func (db::property_names_id_type name_id) const; tl::Variant value_func (const tl::Variant &name) const; tl::Variant values_func (const tl::Variant &name) const; + void put_func (const tl::Variant &name, const tl::Variant &value) const; }; inline db::RecursiveShapeIterator @@ -164,7 +173,7 @@ public: typedef typename ProcessorBase::result_type result_type; property_computation_processor (const Container *container, const std::map &expressions, bool copy_properties, double dbu) - : m_eval (dbu), m_copy_properties (copy_properties), m_expression_strings (expressions) + : m_eval (dbu, true /*with_put*/), m_copy_properties (copy_properties), m_expression_strings (expressions) { if (container) { this->set_result_is_merged (is_merged (container)); @@ -174,7 +183,7 @@ public: // compile the expressions for (auto e = m_expression_strings.begin (); e != m_expression_strings.end (); ++e) { - m_expressions.push_back (std::make_pair (db::property_names_id (e->first), tl::Expression ())); + m_expressions.push_back (std::make_pair (e->first.is_nil () ? db::property_names_id_type (0) : db::property_names_id (e->first), tl::Expression ())); tl::Extractor ex (e->second.c_str ()); m_eval.parse (m_expressions.back ().second, ex); } @@ -187,19 +196,31 @@ public: m_eval.set_prop_id (shape.properties_id ()); m_eval.set_shape (&shape); - db::PropertiesSet ps; + db::PropertiesSet &ps_out = m_eval.prop_set_out (); if (m_copy_properties) { - ps = db::properties (shape.properties_id ()); + ps_out = db::properties (shape.properties_id ()); for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) { - ps.erase (e->first); + if (e->first != db::property_names_id_type (0)) { + ps_out.erase (e->first); + } + } + } else { + ps_out.clear (); + } + + for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) { + if (e->first != db::property_names_id_type (0)) { + ps_out.insert (e->first, e->second.execute ()); } } for (auto e = m_expressions.begin (); e != m_expressions.end (); ++e) { - ps.insert (e->first, e->second.execute ()); + if (e->first == db::property_names_id_type (0)) { + e->second.execute (); + } } - res.back ().properties_id (db::properties_id (ps)); + res.back ().properties_id (db::properties_id (ps_out)); } public: @@ -224,7 +245,7 @@ public: typedef typename FilterBase::shape_type shape_type; expression_filter (const std::string &expression, bool inverse, double dbu) - : m_eval (dbu), m_inverse (inverse), m_expression_string (expression) + : m_eval (dbu, false /*without put func*/), m_inverse (inverse), m_expression_string (expression) { m_eval.init (); diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 3347b9c79..a294d90da 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -282,6 +282,14 @@ new_pcp (const db::Region *container, const std::map & return new property_computation_processor (container, expressions, copy_properties, dbu); } +property_computation_processor * +new_pcps (const db::Region *container, const std::string &expression, bool copy_properties, double dbu) +{ + std::map expressions; + expressions.insert (std::make_pair (tl::Variant (), expression)); + return new property_computation_processor (container, expressions, copy_properties, dbu); +} + Class > decl_PolygonPropertiesExpressions (decl_PolygonProcessorBase, "db", "PolygonPropertiesExpressions", property_computation_processor::method_decls (true) + gsi::constructor ("new", &new_pcp, gsi::arg ("region"), gsi::arg ("expressions"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), @@ -291,18 +299,31 @@ Class > dec "@param expressions A map of property names and expressions used to generate the values of the properties (see class description for details).\n" "@param copy_properties If true, new properties will be added to existing ones.\n" "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" + ) + + gsi::constructor ("new", &new_pcps, gsi::arg ("region"), gsi::arg ("expression"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), + "@brief Creates a new properties expressions operator\n" + "\n" + "@param region The region, the processor will be used on. Can be nil, but if given, allows some optimization.\n" + "@param expression A single expression evaluated for each shape (see class description for details).\n" + "@param copy_properties If true, new properties will be added to existing ones.\n" + "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" ), - "@brief An operator attaching computed properties to the polygons\n" + "@brief An operator attaching computed properties to the edge pairs\n" "\n" "This operator will execute a number of expressions and attach the results as new properties. " - "The expression inputs can be taken either from the polygons themselves or from existing properties.\n" + "The expression inputs can be taken either from the edges themselves or from existing properties.\n" "\n" "A number of expressions can be supplied with a name. The expressions will be evaluated and the result " - "is attached to the output polygons as user properties with the given names.\n" + "is attached to the output edge pairs as user properties with the given names.\n" + "\n" + "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " + "to the output shape.\n" + "\n" "The expression may use the following variables and functions:\n" "\n" "@ul\n" "@li @b shape @/b: The current shape (i.e. 'Polygon' without DBU specified or 'DPolygon' otherwise) @/li\n" + "@li @b put(, ) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b value() @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b values() @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b @/b: A shortcut for 'value()' ( is used as a symbol) @/li\n" diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index 96fd60a95..d68043fe3 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -261,6 +261,14 @@ new_pcp (const db::Texts *container, const std::map &e return new property_computation_processor (container, expressions, copy_properties, dbu); } +property_computation_processor * +new_pcps (const db::Texts *container, const std::string &expression, bool copy_properties, double dbu) +{ + std::map expressions; + expressions.insert (std::make_pair (tl::Variant (), expression)); + return new property_computation_processor (container, expressions, copy_properties, dbu); +} + Class > decl_TextPropertiesExpressions (decl_TextProcessorBase, "db", "TextPropertiesExpressions", property_computation_processor::method_decls (true) + gsi::constructor ("new", &new_pcp, gsi::arg ("texts"), gsi::arg ("expressions"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), @@ -270,18 +278,31 @@ Class > decl_Te "@param expressions A map of property names and expressions used to generate the values of the properties (see class description for details).\n" "@param copy_properties If true, new properties will be added to existing ones.\n" "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" + ) + + gsi::constructor ("new", &new_pcps, gsi::arg ("texts"), gsi::arg ("expression"), gsi::arg ("copy_properties", false), gsi::arg ("dbu", 0.0), + "@brief Creates a new properties expressions operator\n" + "\n" + "@param texts The text collection, the processor will be used on. Can be nil, but if given, allows some optimization.\n" + "@param expression A single expression evaluated for each shape (see class description for details).\n" + "@param copy_properties If true, new properties will be added to existing ones.\n" + "@param dbu If not zero, this value specifies the database unit to use. If given, the shapes returned by the 'shape' function will be micrometer-unit objects.\n" ), - "@brief An operator attaching computed properties to the texts\n" + "@brief An operator attaching computed properties to the edge pairs\n" "\n" "This operator will execute a number of expressions and attach the results as new properties. " - "The expression inputs can be taken either from the texts themselves or from existing properties.\n" + "The expression inputs can be taken either from the edges themselves or from existing properties.\n" "\n" "A number of expressions can be supplied with a name. The expressions will be evaluated and the result " - "is attached to the output texts as user properties with the given names.\n" + "is attached to the output edge pairs as user properties with the given names.\n" + "\n" + "Alternatively, a single expression can be given. In that case, 'put' needs to be used to attach properties " + "to the output shape.\n" + "\n" "The expression may use the following variables and functions:\n" "\n" "@ul\n" "@li @b shape @/b: The current shape (i.e. 'Text' without DBU specified or 'DText' otherwise) @/li\n" + "@li @b put(, ) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n" "@li @b value() @/b: The value of the property with the given name (the first one if there are multiple properties with the same name) @/li\n" "@li @b values() @/b: All values of the properties with the given name (returns a list) @/li\n" "@li @b @/b: A shortcut for 'value()' ( is used as a symbol) @/li\n" diff --git a/testdata/ruby/dbEdgePairsTest.rb b/testdata/ruby/dbEdgePairsTest.rb index 48bbe93bd..c97fadcb3 100644 --- a/testdata/ruby/dbEdgePairsTest.rb +++ b/testdata/ruby/dbEdgePairsTest.rb @@ -707,6 +707,10 @@ class DBEdgePairs_TestClass < TestBase pr = RBA::EdgePairPropertiesExpressions::new(r, { "X" => "PropA+1", "Y" => "shape.distance", "Z" => "value(1)+1" }) assert_equal(r.processed(pr).to_s, "(0,0;1000,2000)/(-100,200;900,2200){X=>18,Y=>179,Z=>43}") + # replace (with 'put') + pr = RBA::EdgePairPropertiesExpressions::new(r, "put('X', PropA+1); put('Y', shape.distance); put('Z', value(1)+1)") + assert_equal(r.processed(pr).to_s, "(0,0;1000,2000)/(-100,200;900,2200){X=>18,Y=>179,Z=>43}") + # substitutions pr = RBA::EdgePairPropertiesExpressions::new(r, { "PropA" => "0", "X" => "PropA+1", "Y" => "shape.distance", "Z" => "value(1)+1" }, true) assert_equal(r.processed(pr).to_s, "(0,0;1000,2000)/(-100,200;900,2200){1=>42,PropA=>0,X=>18,Y=>179,Z=>43}") diff --git a/testdata/ruby/dbEdgesTest.rb b/testdata/ruby/dbEdgesTest.rb index 01c358418..c12de712f 100644 --- a/testdata/ruby/dbEdgesTest.rb +++ b/testdata/ruby/dbEdgesTest.rb @@ -1115,6 +1115,10 @@ class DBEdges_TestClass < TestBase pr = RBA::EdgePropertiesExpressions::new(r, { "X" => "PropA+1", "Y" => "shape.length", "Z" => "value(1)+1" }) assert_equal(r.processed(pr).to_s, "(0,0;1000,2000){X=>18,Y=>2236,Z=>43}") + # replace (with 'put') + pr = RBA::EdgePropertiesExpressions::new(r, "put('X', PropA+1); put('Y', shape.length); put('Z', value(1)+1)") + assert_equal(r.processed(pr).to_s, "(0,0;1000,2000){X=>18,Y=>2236,Z=>43}") + # substitutions pr = RBA::EdgePropertiesExpressions::new(r, { "PropA" => "0", "X" => "PropA+1", "Y" => "shape.length", "Z" => "value(1)+1" }, true) assert_equal(r.processed(pr).to_s, "(0,0;1000,2000){1=>42,PropA=>0,X=>18,Y=>2236,Z=>43}") diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 24dc12607..88d7c8d4d 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -1710,6 +1710,10 @@ class DBRegion_TestClass < TestBase pr = RBA::PolygonPropertiesExpressions::new(r, { "X" => "PropA+1", "Y" => "shape.area", "Z" => "value(1)+1" }) assert_equal(r.processed(pr).to_s, "(0,0;0,2000;1000,2000;1000,0){X=>18,Y=>2000000,Z=>43}") + # replace (with 'put') + pr = RBA::PolygonPropertiesExpressions::new(r, "put('X', PropA+1); put('Y', shape.area); put('Z', value(1)+1)") + assert_equal(r.processed(pr).to_s, "(0,0;0,2000;1000,2000;1000,0){X=>18,Y=>2000000,Z=>43}") + # substitutions pr = RBA::PolygonPropertiesExpressions::new(r, { "PropA" => "0", "X" => "PropA+1", "Y" => "shape.area", "Z" => "value(1)+1" }, true) assert_equal(r.processed(pr).to_s, "(0,0;0,2000;1000,2000;1000,0){1=>42,PropA=>0,X=>18,Y=>2000000,Z=>43}") diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 01f0e964d..098039284 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -557,6 +557,10 @@ class DBTexts_TestClass < TestBase pr = RBA::TextPropertiesExpressions::new(r, { "X" => "PropA+1", "Y" => "shape.bbox.center.x", "Z" => "value(1)+1" }) assert_equal(r.processed(pr).to_s, "('T',r0 100,200){X=>18,Y=>100,Z=>43}") + # replace (with 'put') + pr = RBA::TextPropertiesExpressions::new(r, "put('X', PropA+1); put('Y', shape.bbox.center.x); put('Z', value(1)+1)") + assert_equal(r.processed(pr).to_s, "('T',r0 100,200){X=>18,Y=>100,Z=>43}") + # substitutions pr = RBA::TextPropertiesExpressions::new(r, { "PropA" => "0", "X" => "PropA+1", "Y" => "shape.bbox.center.x", "Z" => "value(1)+1" }, true) assert_equal(r.processed(pr).to_s, "('T',r0 100,200){1=>42,PropA=>0,X=>18,Y=>100,Z=>43}")