'put' function is property computation processors

This commit is contained in:
Matthias Koefferlein 2025-07-29 21:11:34 +02:00
parent 792c06b3a2
commit c0bdc2874a
10 changed files with 177 additions and 19 deletions

View File

@ -261,6 +261,15 @@ new_pcp (const db::EdgePairs *container, const std::map<tl::Variant, std::string
return new property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs> (container, expressions, copy_properties, dbu);
}
static
property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs> *
new_pcps (const db::EdgePairs *container, const std::string &expression, bool copy_properties, double dbu)
{
std::map<tl::Variant, std::string> expressions;
expressions.insert (std::make_pair (tl::Variant (), expression));
return new property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs> (container, expressions, copy_properties, dbu);
}
Class<property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs> > decl_EdgePairPropertiesExpressions (decl_EdgePairProcessorBase, "db", "EdgePairPropertiesExpressions",
property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs>::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<property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs> >
"@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<property_computation_processor<db::EdgePairProcessorBase, db::EdgePairs> >
"\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(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b value(<name>) @/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(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -266,6 +266,15 @@ new_pcp (const db::Edges *container, const std::map<tl::Variant, std::string> &e
return new property_computation_processor<db::EdgeProcessorBase, db::Edges> (container, expressions, copy_properties, dbu);
}
static
property_computation_processor<db::EdgeProcessorBase, db::Edges> *
new_pcps (const db::Edges *container, const std::string &expression, bool copy_properties, double dbu)
{
std::map<tl::Variant, std::string> expressions;
expressions.insert (std::make_pair (tl::Variant (), expression));
return new property_computation_processor<db::EdgeProcessorBase, db::Edges> (container, expressions, copy_properties, dbu);
}
Class<property_computation_processor<db::EdgeProcessorBase, db::Edges> > decl_EdgePropertiesExpressions (decl_EdgeProcessorBase, "db", "EdgePropertiesExpressions",
property_computation_processor<db::EdgeProcessorBase, db::Edges>::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<property_computation_processor<db::EdgeProcessorBase, db::Edges> > 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(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b value(<name>) @/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(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -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<tl::Variant> &args, const std::map<std::string, tl::Variant> * /*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);
}
}

View File

@ -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<tl::Variant, std::string> &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 ();

View File

@ -282,6 +282,14 @@ new_pcp (const db::Region *container, const std::map<tl::Variant, std::string> &
return new property_computation_processor<db::PolygonProcessorBase, db::Region> (container, expressions, copy_properties, dbu);
}
property_computation_processor<db::PolygonProcessorBase, db::Region> *
new_pcps (const db::Region *container, const std::string &expression, bool copy_properties, double dbu)
{
std::map<tl::Variant, std::string> expressions;
expressions.insert (std::make_pair (tl::Variant (), expression));
return new property_computation_processor<db::PolygonProcessorBase, db::Region> (container, expressions, copy_properties, dbu);
}
Class<property_computation_processor<db::PolygonProcessorBase, db::Region> > decl_PolygonPropertiesExpressions (decl_PolygonProcessorBase, "db", "PolygonPropertiesExpressions",
property_computation_processor<db::PolygonProcessorBase, db::Region>::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<property_computation_processor<db::PolygonProcessorBase, db::Region> > 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(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b value(<name>) @/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(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -261,6 +261,14 @@ new_pcp (const db::Texts *container, const std::map<tl::Variant, std::string> &e
return new property_computation_processor<db::TextProcessorBase, db::Texts> (container, expressions, copy_properties, dbu);
}
property_computation_processor<db::TextProcessorBase, db::Texts> *
new_pcps (const db::Texts *container, const std::string &expression, bool copy_properties, double dbu)
{
std::map<tl::Variant, std::string> expressions;
expressions.insert (std::make_pair (tl::Variant (), expression));
return new property_computation_processor<db::TextProcessorBase, db::Texts> (container, expressions, copy_properties, dbu);
}
Class<property_computation_processor<db::TextProcessorBase, db::Texts> > decl_TextPropertiesExpressions (decl_TextProcessorBase, "db", "TextPropertiesExpressions",
property_computation_processor<db::TextProcessorBase, db::Texts>::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<property_computation_processor<db::TextProcessorBase, db::Texts> > 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(<name>, <value>) @/b: Attaches the given value as a property with name 'name' to the output shape @/li\n"
"@li @b value(<name>) @/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(<name>) @/b: All values of the properties with the given name (returns a list) @/li\n"
"@li @b <name> @/b: A shortcut for 'value(<name>)' (<name> is used as a symbol) @/li\n"

View File

@ -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}")

View File

@ -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}")

View File

@ -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}")

View File

@ -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}")