From 250b3790dd89c6619e2237fbdd8e5b4f8caf39cc Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Tue, 28 Oct 2025 15:32:21 +0100 Subject: [PATCH 1/7] Doc: Move doc comments in App::Expression Several doc comments in App/Expression.cpp are moved to header files. --- src/App/Expression.cpp | 162 ++++++------------------------------- src/App/Expression.h | 25 ++++++ src/App/ExpressionParser.h | 16 ++++ 3 files changed, 64 insertions(+), 139 deletions(-) diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index d87d381dae..6ff0880582 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -1012,7 +1012,7 @@ ExpressionPtr Expression::importSubNames(const std::map if(key.second.empty() || subNameMap.contains(key)) continue; std::string imported = PropertyLinkBase::tryImportSubName( - obj,key.second.c_str(),owner->getDocument(), nameMap); + obj, key.second.c_str(), owner->getDocument(), nameMap); if(!imported.empty()) subNameMap.emplace(std::move(key),std::move(imported)); } @@ -1212,14 +1212,6 @@ void UnitExpression::setQuantity(const Quantity &_quantity) } } -/** - * Set unit information. - * - * @param _unit A unit object - * @param _unitstr The unit expressed as a string - * @param _scaler Scale factor to convert unit into internal unit. - */ - void UnitExpression::setUnit(const Quantity &_quantity) { quantity = _quantity; @@ -1230,33 +1222,16 @@ void UnitExpression::setUnit(const Quantity &_quantity) } } -/** - * Simplify the expression. In this case, a NumberExpression is returned, - * as it cannot be simplified any more. - */ - Expression *UnitExpression::simplify() const { return new NumberExpression(owner, quantity); } -/** - * Return a string representation, in this case the unit string. - */ - -/** - * Return a string representation of the expression. - */ - void UnitExpression::_toString(std::ostream &ss, bool,int) const { ss << unitStr; } -/** - * Return a copy of the expression. - */ - Expression *UnitExpression::_copy() const { return new UnitExpression(owner, quantity, unitStr); @@ -1279,29 +1254,16 @@ NumberExpression::NumberExpression(const DocumentObject *_owner, const Quantity { } -/** - * Simplify the expression. For NumberExpressions, we return a copy(), as it cannot - * be simplified any more. - */ - Expression *NumberExpression::simplify() const { return copy(); } -/** - * Create and return a copy of the expression. - */ - Expression *NumberExpression::_copy() const { return new NumberExpression(owner, getQuantity()); } -/** - * Negate the stored value. - */ - void NumberExpression::negate() { setQuantity(-getQuantity()); @@ -1352,10 +1314,6 @@ OperatorExpression::~OperatorExpression() delete right; } -/** - * Determine whether the expression is touched or not, i.e relies on properties that are touched. - */ - bool OperatorExpression::isTouched() const { return left->isTouched() || right->isTouched(); @@ -1477,14 +1435,6 @@ Py::Object OperatorExpression::_getPyValue() const { return calc(this,op,left,right,false); } -/** - * Simplify the expression. For OperatorExpressions, we return a NumberExpression if - * both the left and right side can be simplified to NumberExpressions. In this case - * we can calculate the final value of the expression. - * - * @returns Simplified expression. - */ - Expression *OperatorExpression::simplify() const { Expression * v1 = left->simplify(); @@ -1500,12 +1450,6 @@ Expression *OperatorExpression::simplify() const return new OperatorExpression(owner, v1, op, v2); } -/** - * Create a string representation of the expression. - * - * @returns A string representing the expression. - */ - void OperatorExpression::_toString(std::ostream &s, bool persistent,int) const { bool needsParens; @@ -1607,22 +1551,11 @@ void OperatorExpression::_toString(std::ostream &s, bool persistent,int) const right->toString(s,persistent); } -/** - * A deep copy of the expression. - */ - Expression *OperatorExpression::_copy() const { return new OperatorExpression(owner, left->copy(), op, right->copy()); } -/** - * Return the operators priority. This is used to add parentheses where - * needed when creating a string representation of the expression. - * - * @returns The operator's priority. - */ - int OperatorExpression::priority() const { switch (op) { @@ -1849,13 +1782,6 @@ FunctionExpression::~FunctionExpression() } } -/** - * Determine whether the expressions is considered touched, i.e one or both of its arguments - * are touched. - * - * @return True if touched, false if not. - */ - bool FunctionExpression::isTouched() const { std::vector::const_iterator i = args.begin(); @@ -2657,12 +2583,6 @@ Py::Object FunctionExpression::_getPyValue() const { return evaluate(this,f,args); } -/** - * Try to simplify the expression, i.e calculate all constant expressions. - * - * @returns A simplified expression. - */ - Expression *FunctionExpression::simplify() const { size_t numerics = 0; @@ -2691,12 +2611,6 @@ Expression *FunctionExpression::simplify() const std::move(simplifiedArgs)); } -/** - * Create a string representation of the expression. - * - * @returns A string representing the expression. - */ - void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const { switch (f) { @@ -2847,12 +2761,6 @@ void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const ss << ')'; } -/** - * Create a copy of the expression. - * - * @returns A deep copy of the expression. - */ - Expression *FunctionExpression::_copy() const { std::vector::const_iterator i = args.begin(); @@ -2890,30 +2798,11 @@ VariableExpression::VariableExpression(const DocumentObject *_owner, const Objec VariableExpression::~VariableExpression() = default; -/** - * Determine if the expression is touched or not, i.e whether the Property object it - * refers to is touched(). - * - * @returns True if the Property object is touched, false if not. - */ - bool VariableExpression::isTouched() const { return var.isTouched(); } -/** - * Find the property this expression referse to. - * - * Unqualified names (i.e the name only without any dots) are resolved in the owning DocumentObjects. - * Qualified names are looked up in the owning Document. It is first looked up by its internal name. - * If not found, the DocumentObjects' labels searched. - * - * If something fails, an exception is thrown. - * - * @returns The Property object if it is derived from either PropertyInteger, PropertyFloat, or PropertyString. - */ - const Property * VariableExpression::getProperty() const { const Property * prop = var.getProperty(); @@ -2985,22 +2874,11 @@ void VariableExpression::_toString(std::ostream &ss, bool persistent,int) const ss << var.toString(); } -/** - * Simplify the expression. Simplification of VariableExpression objects is - * not possible (if it is instantiated it would be an evaluation instead). - * - * @returns A copy of the expression. - */ - Expression *VariableExpression::simplify() const { return copy(); } -/** - * Return a copy of the expression. - */ - Expression *VariableExpression::_copy() const { return new VariableExpression(owner, var); @@ -3201,10 +3079,6 @@ StringExpression::~StringExpression() { } } -/** - * Simplify the expression. For strings, this is a simple copy of the object. - */ - Expression *StringExpression::simplify() const { return copy(); @@ -3215,10 +3089,6 @@ void StringExpression::_toString(std::ostream &ss, bool,int) const ss << quote(text); } -/** - * Return a copy of the expression. - */ - Expression *StringExpression::_copy() const { return new StringExpression(owner, text); @@ -3548,9 +3418,10 @@ bool isModuleImported(PyObject *module) { } /** - * Error function for parser. Throws a generic Base::Exception with the parser error. + * @brief Error function for parser. + * + * @throws Base::Exception A generic parser error. */ - void ExpressionParser_yyerror(const char *errorinfo) { (void)errorinfo; @@ -3586,12 +3457,24 @@ double num_change(char* yytext,char dez_delim,char grp_delim) return ret_val; } -static Expression * ScanResult = nullptr; /**< The resulting expression after a successful parsing */ -static const App::DocumentObject * DocumentObject = nullptr; /**< The DocumentObject that will own the expression */ -static bool unitExpression = false; /**< True if the parsed string is a unit only */ -static bool valueExpression = false; /**< True if the parsed string is a full expression */ -static std::stack labels; /**< Label string primitive */ -static std::map registered_functions; /**< Registered functions */ +/// The resulting expression after a successful parsing. +static Expression* ScanResult = nullptr; + +/// The DocumentObject that will own the expression. +static const App::DocumentObject* DocumentObject = nullptr; + +/// Whether the parsed string is a unit only. +static bool unitExpression = false; + +/// Whether the parsed string is a full expression. +static bool valueExpression = false; + +/// Label string primitive. +static std::stack labels; + +/// Registered functions during parsing. +static std::map registered_functions; + static int last_column; static int column; @@ -3885,3 +3768,4 @@ bool ExpressionParser::isTokenAUnit(const std::string & str) #if defined(__clang__) # pragma clang diagnostic pop #endif + diff --git a/src/App/Expression.h b/src/App/Expression.h index 1b416c4ca8..ec9268596a 100644 --- a/src/App/Expression.h +++ b/src/App/Expression.h @@ -127,6 +127,14 @@ public: ~Expression() override; + /** + * @brief Check if the expression is touched. + * + * An expression is touched if one of the properties it references is + * touched. + * + * @return true if the expression is touched, false otherwise. + */ virtual bool isTouched() const { return false; } Expression * eval() const; @@ -138,6 +146,14 @@ public: Expression * copy() const; + /** + * @brief Get the operator priority. + * + * This is used to determine whether parentheses are needed when + * converting the expression to a string. + * + * @return The operator priority. + */ virtual int priority() const; void getIdentifiers(std::map &) const; @@ -164,6 +180,15 @@ public: bool adjustLinks(const std::set &inList); + /** + * @brief Simplify the expression. + * + * In contrast to @ref eval, which evaluates the expression to a value, + * this function simplifies the expression by computing all constant + * expressions. + * + * @return The simplified expression. + */ virtual Expression * simplify() const = 0; void visit(ExpressionVisitor & v); diff --git a/src/App/ExpressionParser.h b/src/App/ExpressionParser.h index 1cabea5ccb..c109862f33 100644 --- a/src/App/ExpressionParser.h +++ b/src/App/ExpressionParser.h @@ -143,6 +143,9 @@ public: Expression* simplify() const override; + /** + * @brief Negate the stored value. + */ void negate(); bool isInteger(long* v = nullptr) const; @@ -473,6 +476,19 @@ public: void setPath(const ObjectIdentifier& path); + /** + * @brief Find the property this expression referse to. + * + * Unqualified names (i.e the name only without any dots) are resolved in + * the owning DocumentObjects. Qualified names are looked up in the owning + * Document, first, by its internal name, then if not found, by the + * DocumentObjects' labels. + * + * @return The Property object if it is derived from either + * PropertyInteger, PropertyFloat, or PropertyString. + * + * @trhows Expression::Exception If the property cannot be resolved. + */ const App::Property* getProperty() const; void addComponent(Component* component) override; From 6afb6f7071e08a57b814c2eb470683302a6dd888 Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Tue, 28 Oct 2025 15:44:53 +0100 Subject: [PATCH 2/7] Doc: Improve the App::Expression documentation --- src/App/Expression.h | 390 ++++++++++++++++++++++++++++++++++--- src/App/ExpressionParser.h | 3 + src/App/GeoFeature.cpp | 22 +-- src/App/GeoFeature.h | 28 +-- src/App/PropertyLinks.h | 54 ++--- 5 files changed, 417 insertions(+), 80 deletions(-) diff --git a/src/App/Expression.h b/src/App/Expression.h index ec9268596a..75faab5875 100644 --- a/src/App/Expression.h +++ b/src/App/Expression.h @@ -49,41 +49,216 @@ class Document; using ExpressionPtr = std::unique_ptr; -AppExport bool isAnyEqual(const App::any &v1, const App::any &v2); + +/** + * @brief Check whether two values are equal. + * + * @param[in] v1 The first value. + * @param[in] v2 The second value. + * @return true if the values are equal, false otherwise. + */ +AppExport bool isAnyEqual(const App::any& v1, const App::any& v2); + +/** + * @brief Convert an App::any value to a Base::Quantity. + * + * @param[in] value The value to convert. + * @param[in] errmsg Optional error message to use in case of failure. + * + * @return The converted Base::Quantity. + * @throw Base::TypeError if the value cannot be converted to a Base::Quantity. + */ AppExport Base::Quantity anyToQuantity(const App::any &value, const char *errmsg = nullptr); // clang-format off -// Map of depending objects to a map of depending property name to the full referencing object identifier -using ExpressionDeps = std::map > >; +/// Map of depending objects to a map of depending property name to the full referencing object identifier +using ExpressionDeps = std::map>>; // clang-format on +/** + * @brief %Base class for expression visitors. + * @ingroup ExpressionFramework + * + * This is a class that provides functionality to define visitors for + * expressions. For a high-level overview of the %Expression framework see + * topic @ref ExpressionFramework "Expression Framework". + */ class AppExport ExpressionVisitor { public: virtual ~ExpressionVisitor() = default; - virtual void visit(Expression &e) = 0; + + /** + * @brief Visit an expression. + * + * This is the method that is called when visiting an expression. + * + * @param[in,out] e The expression to visit. + */ + virtual void visit(Expression& e) = 0; + + /// Called before starting modifications virtual void aboutToChange() {} + + /** + * @brief Called after a modification has been made. + * + * @return The number of changes made. + */ virtual int changed() const { return 0;} + + /// Reset the change counter. virtual void reset() {} + + /// Get the property link if applicable. virtual App::PropertyLinkBase* getPropertyLink() {return nullptr;} protected: - void getIdentifiers(Expression &e, std::map &); - bool adjustLinks(Expression &e, const std::set &inList); - bool relabeledDocument(Expression &e, const std::string &oldName, const std::string &newName); - bool renameObjectIdentifier(Expression &e, - const std::map &, const ObjectIdentifier &); - void collectReplacement(Expression &e, std::map &, - const App::DocumentObject *parent, App::DocumentObject *oldObj, App::DocumentObject *newObj) const; - bool updateElementReference(Expression &e, App::DocumentObject *feature,bool reverse); - void importSubNames(Expression &e, const ObjectIdentifier::SubNameMap &subNameMap); - void updateLabelReference(Expression &e, App::DocumentObject *obj, - const std::string &ref, const char *newLabel); - void moveCells(Expression &e, const CellAddress &address, int rowCount, int colCount); + /** + * @brief Get the identifiers used in an expression. + * + * @param[in] e The expression. + * @param[in, out] ids The map to fill with identifiers. + */ + void getIdentifiers(Expression& e, std::map& ids); + + /** + * @brief Adjust the links in an expression. + * + * @param[in,out] e The expression. + * @param[in] inList The set of document objects to adjust links for. + * @return true if any links were adjusted, false otherwise. + */ + bool adjustLinks(Expression& e, const std::set& inList); + + /** + * @brief Update label references in an expression. + * + * @param[in,out] e The expression. + * @param[in] oldName The old document name. + * @param[in] newName The new document name. + * @return true if any label references were updated, false otherwise. + */ + bool relabeledDocument(Expression& e, const std::string& oldName, const std::string& newName); + + /** + * @brief Rename object identifiers in an expression. + * + * Object identifiers are renamed according to the provided map of old to + * new object identifiers. The new identifiers are made relative to @p + * path. + * + * @param[in,out] e The expression. + * @param[in] paths The map of old to new object identifiers. + * @param[in] path The object identifier path context. + * + * @return True if any object identifiers were renamed, false otherwise. + */ + bool renameObjectIdentifier(Expression& e, + const std::map& paths, + const ObjectIdentifier& path); + + /** + * @brief Collect replacements in an expression. + * + * This function runs before the @ref renameObjectIdentifier function and + * collects all occurrences of a specific old document object + * and maps them to a new document object in the provided map. + * + * @param[in,out] e The expression. + * @param[in,out] paths The map to fill with old to new object identifier replacements. + * @param[in] parent The parent document object. + * @param[in] oldObj The old document object to be replaced. + * @param[in] newObj The new document object to replace with. + */ + void collectReplacement(Expression& e, + std::map& paths, + const App::DocumentObject* parent, + App::DocumentObject* oldObj, + App::DocumentObject* newObj) const; + + /** + * @brief Update element references in an expression. + * + * @param[in,out] e The expression. + * @param[in] feature The document object feature for which the update + * element references should be updated. + * @param[in] reverse If true, use the old style, i.e. non-mapped element + * reference to query for the new style, i.e. mapped element reference when + * update. If false, then the other way around. + * + * @return true if any element references were updated, false otherwise. + */ + bool updateElementReference(Expression& e, App::DocumentObject* feature, bool reverse); + + /** + * @brief Rewrite sub-names in an expression after importing external + * objects. + * + * @param[in,out] e The expression. + * @param[in] subNameMap The map of sub-name replacements. + */ + void importSubNames(Expression& e, const ObjectIdentifier::SubNameMap& subNameMap); + + /** + * @brief Update label references in an expression. + * + * @param[in,out] e The expression. + * @param[in] obj The document object whose label has changed. + * @param[in] ref The old label reference. + * @param[in] newLabel The new label. + */ + void updateLabelReference(Expression& e, + App::DocumentObject* obj, + const std::string& ref, + const char* newLabel); + + /** + * @brief Move cell references in an expression. + * + * This function visits the expression and updates all cell references to + * @p address. These cell references are then updated by moving them by @p + * rowCount rows and @p colCount columns. + * + * @param[in,out] e The expression. + * @param[in] address The cell address that needs to be updated. + * @param[in] rowCount The number of rows to move. + * @param[in] colCount The number of columns to move. + */ + void moveCells(Expression& e, const CellAddress& address, int rowCount, int colCount); + + /** + * @brief Offset cell references in an expression. + * + * This function visits the expression and in a range expression, offsets + * all cell relative references by @p rowOffset rows and @p colOffset + * columns. + * + * @param[in,out] e The expression. + * @param[in] rowOffset The number of rows to offset. + * @param[in] colOffset The number of columns to offset. + */ void offsetCells(Expression &e, int rowOffset, int colOffset); }; +/** + * @brief Class for expression visitors that can modify expressions. + * @ingroup ExpressionFramework + * + * With respect to the base class @ref ExpressionVisitor, this class adds + * functionality to modify the visited @ref Expression objects and signal on + * changes. It also keeps track of the number of modifications made to the + * visited expressions. For a high-level overview of the %Expression framework + * see topic @ref ExpressionFramework "Expression Framework". + * + * @tparam P The property type being modified. + */ template class ExpressionModifier : public ExpressionVisitor { public: + /** + * @brief Construct a new ExpressionModifier object. + * + * @param[in,out] _prop The property being modified. + */ explicit ExpressionModifier(P & _prop) : prop(_prop) , propLink(freecad_cast(&prop)) @@ -104,9 +279,16 @@ public: App::PropertyLinkBase* getPropertyLink() override {return propLink;} protected: - P & prop; - App::PropertyLinkBase *propLink; + /// The property being modified. + P& prop; + + /// The property link if applicable. + App::PropertyLinkBase* propLink; + + /// The atomic property change signaller. typename AtomicPropertyChangeInterface

::AtomicPropertyChange signaller; + + /// The number of changes made. int _changed{0}; }; @@ -122,7 +304,11 @@ class AppExport Expression : public Base::BaseClass { TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: - + /** + * @brief Construct a new Expression object. + * + * @param[in] _owner The document object that owns this expression. + */ explicit Expression(const App::DocumentObject * _owner); ~Expression() override; @@ -137,13 +323,49 @@ public: */ virtual bool isTouched() const { return false; } - Expression * eval() const; + /** + * @brief Evaluate the expression. + * + * Evaluating an expression returns another expression that represents a + * value. Contrast this with the @ref simplify function that returns a + * possibly simpler version of the same expression. + * + * @return The evaluated expression. + */ + Expression* eval() const; - std::string toString(bool persistent=false, bool checkPriority=false, int indent=0) const; + /** + * @brief Convert the expression to a string. + * + * @param[in] persistent If true, the string representation is persistent + * and can be saved to a file. + * @param[in] checkPriority If true, check whether the expression requires + * parentheses based on operator priority. + * @param[in] indent The indentation level for pretty-printing. + * + * @return The string representation of the expression. + */ + std::string toString(bool persistent = false, bool checkPriority = false, int indent = 0) const; + + /** + * @brief Write a string representation of the expression to a stream. + * + * @param[in,out] os The output stream to write to. + * @copydoc Expression::toString(bool, bool, int) const + */ void toString(std::ostream &os, bool persistent=false, bool checkPriority=false, int indent=0) const; - static Expression * parse(const App::DocumentObject * owner, const std::string& buffer); + /** + * @brief Parse an expression from a string. + * + * @param[in] owner The document object that will own the parsed expression. + * @param[in] buffer The string to parse. + * + * @return The parsed expression. + */ + static Expression* parse(const App::DocumentObject * owner, const std::string& buffer); + /// Copy an expression. Expression * copy() const; /** @@ -156,28 +378,104 @@ public: */ virtual int priority() const; - void getIdentifiers(std::map &) const; + /** + * @brief Get the identifiers in the expression. + * + * @param[in,out] deps The mapping from object identifiers to a boolean + * indicating whether the dependency is hidden (`href` references). + */ + void getIdentifiers(std::map& deps) const; + + /** + * @brief Get the identifiers in the expression. + * + * @return The mapping from object identifiers to a boolean indicating + * whether the dependency is hidden (`href` references). + */ std::map getIdentifiers() const; - enum DepOption { - DepNormal, - DepHidden, - DepAll, + /// Options for obtaining the dependencies of an expression. + enum DepOption + { + DepNormal, ///< Get normal dependencies. + DepHidden, ///< Get the hidden dependencies (`href` references). + DepAll, ///< Get both normal and hidden dependencies. }; - void getDeps(ExpressionDeps &deps, int option=DepNormal) const; + + /** + * @brief Get the dependencies of the expression. + * + * @param[in,out] deps The dependencies to fill. + * @param[in] option The dependency option. + */ + void getDeps(ExpressionDeps& deps, int option = DepNormal) const; + + /** + * @brief Get the dependencies of the expression. + * + * @param[in] option The dependency option. + * @return The dependencies. + */ ExpressionDeps getDeps(int option=DepNormal) const; - std::map getDepObjects(std::vector *labels=nullptr) const; - void getDepObjects(std::map &, std::vector *labels=nullptr) const; + /** + * @brief Get the dependent document objects of the expression. + * + * @param[in,out] labels Optional vector to fill with labels of dependent objects. + * + * @return Map of dependent document objects to a boolean indicating whether + * they are hidden (`href` references). + */ + std::map getDepObjects(std::vector* labels = nullptr) const; + /** + * @brief Get the dependent document objects of the expression. + * + * @param[in,out] deps The map to fill with dependent document objects to a + * boolean indicating whether they are hidden (`href` references). + * @param[in,out] labels Optional vector to fill with labels of dependent objects. + */ + void getDepObjects(std::map& deps, std::vector *labels=nullptr) const; + + /** + * @brief Import sub-names in the expression after importing external objects. + * + * @param[in] nameMap The map of sub-name replacements. + * + * @return The updated expression. + */ ExpressionPtr importSubNames(const std::map &nameMap) const; + /** + * @brief Update label references in the expression. + * + * @param[in] obj The document object whose label has changed. + * @param[in] ref The old label reference. + * @param[in] newLabel The new label. + * + * @return The updated expression. + */ ExpressionPtr updateLabelReference(App::DocumentObject *obj, const std::string &ref, const char *newLabel) const; + /** + * @brief Replace a document object in the expression. + * + * @param[in] parent The parent document object. + * @param[in] oldObj The old document object to be replaced. + * @param[in] newObj The new document object to replace with. + * + * @return The updated expression. + */ ExpressionPtr replaceObject(const App::DocumentObject *parent, App::DocumentObject *oldObj, App::DocumentObject *newObj) const; + /** + * @brief Adjust links according to a new inList. + * + * @param[in] inList The new inList to adjust links for. + * @return true if any links were adjusted, false otherwise. + */ bool adjustLinks(const std::set &inList); /** @@ -191,30 +489,55 @@ public: */ virtual Expression * simplify() const = 0; + /// Visit the expression with a visitor. void visit(ExpressionVisitor & v); + /// Exception class for expression errors. class Exception : public Base::Exception { public: explicit Exception(const char *sMessage) : Base::Exception(sMessage) { } }; + /// Get the owner of the expression. App::DocumentObject * getOwner() const { return owner; } + struct Component; + /// Add a component to the expression. virtual void addComponent(Component* component); using ComponentList = std::vector; - static Component *createComponent(const std::string &n); + /// Create a component from a name. + static Component* createComponent(const std::string& n); + + /** + * @brief Create an index or range component. + * + * @param[in] e1 Either the index in an array index or the start of a + * range. + * + * @param[in] e2 The end of a range, may be `nullptr`. + * @param[in] e3 The step of a range, may be `nullptr`. + */ static Component *createComponent(Expression *e1, Expression *e2=nullptr, Expression *e3=nullptr, bool isRange=false); + /// Check if the expression has a component. bool hasComponent() const {return !components.empty();} + /// Get the value as boost::any. boost::any getValueAsAny() const; + /// Get the value as a Python object. Py::Object getPyValue() const; + /** + * @brief Check if this expression is the same as another. + * + * @param[in] other The other expression to compare with. + * @param[in] checkComment If true, also check the comment. + */ bool isSame(const Expression &other, bool checkComment=true) const; friend class ExpressionVisitor; @@ -245,8 +568,11 @@ protected: protected: // clang-format off - App::DocumentObject * owner; /**< The document object used to access unqualified variables (i.e local scope) */ + /// The document object used to access unqualified variables (i.e local scope). + App::DocumentObject * owner; + + /// The list of components. ComponentList components; public: diff --git a/src/App/ExpressionParser.h b/src/App/ExpressionParser.h index c109862f33..584e16b76d 100644 --- a/src/App/ExpressionParser.h +++ b/src/App/ExpressionParser.h @@ -42,6 +42,9 @@ namespace App // included by everyone /////////////////////////////////////////////////////////////////////////////////// +/** + * @brief %Part of an expression that represents an index or range. + */ struct AppExport Expression::Component { ObjectIdentifier::Component comp; diff --git a/src/App/GeoFeature.cpp b/src/App/GeoFeature.cpp index 0a4e49a5db..d5a3657ecb 100644 --- a/src/App/GeoFeature.cpp +++ b/src/App/GeoFeature.cpp @@ -139,7 +139,7 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, bool append, ElementNameType type, const DocumentObject* filter, - const char** _element, + const char** element, GeoFeature** geoFeature) { elementName.newName.clear(); @@ -150,11 +150,11 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, if (!subname) { subname = ""; } - const char* element = Data::findElementName(subname); - if (_element) { - *_element = element; + const char* foundElement = Data::findElementName(subname); + if (element) { + *element = foundElement; } - auto sobj = obj->getSubObject(std::string(subname, element).c_str()); + auto sobj = obj->getSubObject(std::string(subname, foundElement).c_str()); if (!sobj) { return nullptr; } @@ -172,16 +172,16 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, if (filter && geo != filter) { return nullptr; } - if (!element || !element[0]) { + if (!foundElement || !foundElement[0]) { if (append) { elementName.oldName = Data::oldElementName(subname); } return sobj; } - if (!geo || hasHiddenMarker(element)) { + if (!geo || hasHiddenMarker(foundElement)) { if (!append) { - elementName.oldName = element; + elementName.oldName = foundElement; } else { elementName.oldName = Data::oldElementName(subname); @@ -189,11 +189,11 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, return sobj; } if (!append) { - elementName = geo->getElementName(element, type); + elementName = geo->getElementName(foundElement, type); } else { - const auto& names = geo->getElementName(element, type); - std::string prefix(subname, element - subname); + const auto& names = geo->getElementName(foundElement, type); + std::string prefix(subname, foundElement - subname); if (!names.newName.empty()) { elementName.newName = prefix + names.newName; } diff --git a/src/App/GeoFeature.h b/src/App/GeoFeature.h index 57eb2fa62f..93a94ecbd8 100644 --- a/src/App/GeoFeature.h +++ b/src/App/GeoFeature.h @@ -93,19 +93,23 @@ public: const char* name, ElementNameType type = Normal) const; - /** Resolve both the new and old style element name + /** + * @brief Resolve both the new and old style element name. * - * @param obj: top parent object - * @param subname: subname reference - * @param elementName: output of a pair(newElementName,oldElementName) - * @param append: Whether to include subname prefix into the returned - * element name - * @param type: the type of element name to request - * @param filter: If none zero, then only perform lookup when the element - * owner object is the same as this filter - * @param element: return the start of element name in subname + * @param[in] obj The top parent object + * @param[in] subname The subname reference. + * @param[out] elementName Output of a pair(newElementName,oldElementName) + * @param[in] append: Whether to include the subname prefix into the + * returned element name. + * @param[in] type The type of element name to request. + * @param[in] filter If not `nullptr`, then only perform lookup when the + * element owner object is the same as this filter. + * @param[out] element If not `nullptr`, provide start of element name in + * subname. + * @param[out] geoFeature If not `nullptr`, provide the GeoFeature that + * contains the element. * - * @return Return the owner object of the element + * @return Return the owner object of the element. */ static DocumentObject* resolveElement(const App::DocumentObject* obj, const char* subname, @@ -114,7 +118,7 @@ public: ElementNameType type = Normal, const DocumentObject* filter = nullptr, const char** element = nullptr, - GeoFeature** geo = nullptr); + GeoFeature** geoFeature = nullptr); /** * @brief Deprecated. Calculates the placement in the global reference coordinate system diff --git a/src/App/PropertyLinks.h b/src/App/PropertyLinks.h index a0af06530d..9ca0ea549d 100644 --- a/src/App/PropertyLinks.h +++ b/src/App/PropertyLinks.h @@ -393,28 +393,31 @@ public: /// Update all element references in all link properties of \a feature static void updateElementReferences(DocumentObject* feature, bool reverse = false); - + /// Update all element references in the _ElementRefMap static void updateAllElementReferences(bool reverse = false); /// Obtain link properties that contain element references to a given object static const std::unordered_set& getElementReferences(DocumentObject*); - /** Helper function for update individual element reference + /** + * @brief Update individual element references. * - * @param feature: if given, than only update element reference belonging - * to this feature. If not, then update geometry element - * references. - * @param sub: the subname reference to be updated. - * @param shadow: a pair of new and old style element references to be updated. - * @param reverse: if true, then use the old style, i.e. non-mapped element - * reference to query for the new style, i.e. mapped - * element reference when update. If false, then the other - * way around. - * @param notify: if true, call aboutToSetValue() before change + * This helper function is to be called by each link property in the event + * of geometry element reference change due to geometry model changes. * - * This helper function is to be called by each link property in the event of - * geometry element reference change due to geometry model changes. + * @param[in] feature If given, than only update element reference + * belonging to this feature. If not, then update all geometry element + * references. + * @param[in] obj The linked object. + * @param[in,out] sub The subname reference to be updated. + * @param[in,out] shadow A pair of new and old style element references to be updated. + * + * @param[in] reverse If true, then use the old style, i.e. non-mapped element + * reference to query for the new style, i.e. mapped element reference when + * update. If false, then the other way around. + * + * @param[in] notify: if true, call aboutToSetValue() before change */ bool _updateElementReference(App::DocumentObject* feature, App::DocumentObject* obj, @@ -446,20 +449,21 @@ public: const std::vector& objs, bool clear); - /** Helper function for link import operation + /** + * @brief Try import a subname reference. * - * @param obj: the linked object - * @param sub: subname reference - * @param doc: importing document - * @param nameMap: a name map from source object to its imported counter part + * This operation will go through all subname references and import all + * externally linked objects. After import, the link property must be + * changed to point to the newly imported objects, which should happen + * inside the API CopyOnImportExternal(). This function helps to rewrite + * subname reference to point to the correct sub objects that are imported. * - * @return Return a changed subname reference, or empty string if no change. + * @param obj The linked object. + * @param sub The subname reference. + * @param doc The importing document. + * @param nameMap A name map from source object to its imported counter part. * - * Link import operation will go through all link property and imports all - * externally linked object. After import, the link property must be - * changed to point to the newly imported objects, which should happen inside - * the API CopyOnImportExternal(). This function helps to rewrite subname - * reference to point to the correct sub objects that are imported. + * @return A changed subname reference, or an empty string if no change. */ static std::string tryImportSubName(const App::DocumentObject* obj, const char* sub, From 8f110b59e749ef45de3aee8394b6f84a49aa6802 Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Tue, 28 Oct 2025 15:45:40 +0100 Subject: [PATCH 3/7] Doc: Move doc comments in PropertyExpressionEngine Several doc comments in App/PropertyExpressionEngine.cpp are moved to header files. --- src/App/PropertyExpressionEngine.cpp | 103 +------------------------ src/App/PropertyExpressionEngine.h | 110 +++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 105 deletions(-) diff --git a/src/App/PropertyExpressionEngine.cpp b/src/App/PropertyExpressionEngine.cpp index 241a93ead7..3f10e2133b 100644 --- a/src/App/PropertyExpressionEngine.cpp +++ b/src/App/PropertyExpressionEngine.cpp @@ -99,28 +99,13 @@ struct PropertyExpressionEngine::Private TYPESYSTEM_SOURCE(App::PropertyExpressionEngine, App::PropertyExpressionContainer) -/** - * @brief Construct a new PropertyExpressionEngine object. - */ - PropertyExpressionEngine::PropertyExpressionEngine() : validator(0) {} -/** - * @brief Destroy the PropertyExpressionEngine object. - */ - PropertyExpressionEngine::~PropertyExpressionEngine() = default; -/** - * @brief Estimate memory size of this property. - * - * \fixme Should probably return something else than 0. - * - * @return Size of object. - */ - +// fixme Should probably return something else than 0. unsigned int PropertyExpressionEngine::getMemSize() const { return 0; @@ -360,15 +345,6 @@ void PropertyExpressionEngine::Restore(Base::XMLReader& reader) reader.readEndElement("ExpressionEngine"); } -/** - * @brief Update graph structure with given path and expression. - * @param path Path - * @param expression Expression to query for dependencies - * @param nodes Map with nodes of graph, including dependencies of 'expression' - * @param revNodes Reverse map of the nodes, containing only the given paths, without dependencies. - * @param edges Edges in graph - */ - void PropertyExpressionEngine::buildGraphStructures( const ObjectIdentifier& path, const std::shared_ptr expression, @@ -410,12 +386,6 @@ void PropertyExpressionEngine::buildGraphStructures( } } -/** - * @brief Create a canonical object identifier of the given object \a p. - * @param p ObjectIndentifier - * @return New ObjectIdentifier - */ - ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier& oid) const { DocumentObject* docObj = freecad_cast(getContainer()); @@ -446,11 +416,6 @@ ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier& return oid.canonicalPath(); } -/** - * @brief Number of expressions managed by this object. - * @return Number of expressions. - */ - size_t PropertyExpressionEngine::numExpressions() const { return expressions.size(); @@ -495,12 +460,6 @@ void PropertyExpressionEngine::onContainerRestored() } } -/** - * @brief Get expression for \a path. - * @param path ObjectIndentifier to query for. - * @return Expression for \a path, or empty boost::any if not found. - */ - const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentifier& path) const { // Get a canonical path @@ -514,13 +473,6 @@ const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentif return boost::any(); } -/** - * @brief Set expression with optional comment for \a path. - * @param path Path to update - * @param expr New expression - * @param comment Optional comment. - */ - void PropertyExpressionEngine::setValue(const ObjectIdentifier& path, std::shared_ptr expr) { @@ -558,11 +510,9 @@ void PropertyExpressionEngine::setValue(const ObjectIdentifier& path, } } -/** - * @brief The cycle_detector struct is used by the boost graph routines to detect cycles in the - * graph. - */ +/* The cycle_detector struct is used by the boost graph routines to detect + * cycles in the graph. */ struct cycle_detector: public boost::dfs_visitor<> { cycle_detector(bool& has_cycle, int& src) @@ -582,14 +532,6 @@ protected: int& _src; }; -/** - * @brief Build a graph of all expressions in \a exprs. - * @param exprs Expressions to use in graph - * @param revNodes Map from int[nodeid] to ObjectIndentifer. - * @param g Graph to update. May contain additional nodes than in revNodes, because of outside - * dependencies. - */ - void PropertyExpressionEngine::buildGraph(const ExpressionMap& exprs, boost::unordered_map& revNodes, DiGraph& g, @@ -641,12 +583,6 @@ void PropertyExpressionEngine::buildGraph(const ExpressionMap& exprs, } } -/** - * The code below builds a graph for all expressions in the engine, and - * finds any circular dependencies. It also computes the internal evaluation - * order, in case properties depends on each other. - */ - std::vector PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option) { @@ -671,11 +607,6 @@ PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option) return evaluationOrder; } -/** - * @brief Compute and update values of all registered expressions. - * @return StdReturn on success. - */ - DocumentObjectExecReturn* App::PropertyExpressionEngine::execute(ExecuteOption option, bool* touched) { @@ -808,12 +739,6 @@ DocumentObjectExecReturn* App::PropertyExpressionEngine::execute(ExecuteOption o return DocumentObject::StdReturn; } -/** - * @brief Find paths to document object. - * @param obj Document object - * @param paths Object identifier - */ - void PropertyExpressionEngine::getPathsToDocumentObject( DocumentObject* obj, std::vector& paths) const @@ -839,11 +764,6 @@ void PropertyExpressionEngine::getPathsToDocumentObject( } } -/** - * @brief Determine whether any dependencies of any of the registered expressions have been touched. - * @return True if at least on dependency has been touched. - */ - bool PropertyExpressionEngine::depsAreTouched() const { for (auto& v : _Deps) { @@ -855,13 +775,6 @@ bool PropertyExpressionEngine::depsAreTouched() const return false; } -/** - * @brief Validate the given path and expression. - * @param path Object Identifier for expression. - * @param expr Expression tree. - * @return Empty string on success, error message on failure. - */ - std::string PropertyExpressionEngine::validateExpression(const ObjectIdentifier& path, std::shared_ptr expr) const @@ -913,11 +826,6 @@ PropertyExpressionEngine::validateExpression(const ObjectIdentifier& path, return {}; } -/** - * @brief Rename paths based on \a paths. - * @param paths Map with current and new object identifier. - */ - void PropertyExpressionEngine::renameExpressions( const std::map& paths) { @@ -950,11 +858,6 @@ void PropertyExpressionEngine::renameExpressions( hasSetValue(); } -/** - * @brief Rename object identifiers in the registered expressions. - * @param paths Map with current and new object identifiers. - */ - void PropertyExpressionEngine::renameObjectIdentifiers( const std::map& paths) { diff --git a/src/App/PropertyExpressionEngine.h b/src/App/PropertyExpressionEngine.h index b1d83e0a56..6b9d8b9b38 100644 --- a/src/App/PropertyExpressionEngine.h +++ b/src/App/PropertyExpressionEngine.h @@ -138,8 +138,30 @@ public: void Restore(Base::XMLReader& reader) override; + /** + * @brief Set a given expression to @a path. + * + * Note that the "value" in this context is an expression. This means that + * this function does not evaluate the expression and updates the property + * that @a path points to. It merely registers the expression to be used + * when evaluating the property later. + * + * @param[in] path The path that the expression is targeting. + * @param[in] expr The new expression. + */ void setValue(const App::ObjectIdentifier& path, std::shared_ptr expr); + /** + * @brief Get the expression for @a path. + * + * Note that the "value" in this context is an expression. This means that + * this function does not return the evaluated value of the property that + * @a path points to. It merely returns the registered expression. + * + * @param[in] path ObjectIndentifier to query for. + * + * @return The expression for @a path, or empty boost::any if not found. + */ const boost::any getPathValue(const App::ObjectIdentifier& path) const override; /// Execute options @@ -154,14 +176,39 @@ public: /// Execute on document restore ExecuteOnRestore, }; - /** Evaluate the expressions + + /** + * @brief Evaluate the expressions. * - * @param option: execution option, see ExecuteOption. + * Evaluate the expressions and update the properties they are bound to. + * + * @param[in] option: execution option, see ExecuteOption. + * @param[out] touched: if not null, set to true if any property was + * changed. + * + * @return On success a pointer to DocumentObject::StdReturn is returned. On failure, it + * returns a pointer to a newly created App::DocumentObjectExecReturn that contains the error + * message. */ DocumentObjectExecReturn* execute(ExecuteOption option = ExecuteAll, bool* touched = nullptr); - void getPathsToDocumentObject(DocumentObject*, std::vector& paths) const; + /** + * @brief Find the paths to a given document object. + * + * @param[in] obj The document object to find paths to. + * @param[out] paths Object identifiers that point to @a obj. + */ + void getPathsToDocumentObject(DocumentObject* obj, + std::vector& paths) const; + /** + * @brief Check if any dependencies are touched. + * + * Determine whether any dependencies of any of the registered expressions + * have been touched. + * + * @return True if at least on dependency has been touched. + */ bool depsAreTouched() const; /* Expression validator */ @@ -170,16 +217,41 @@ public: validator = f; } + /** + * @brief Validate the expression expression for a given path. + * + * @param[in] path The object identifier that the expression is targeting. + * @param expr The expression to validate. + * + * @return An empty string on success, an error message on failure. + */ std::string validateExpression(const App::ObjectIdentifier& path, std::shared_ptr expr) const; + /** + * @brief Rename object identifiers in the registered expressions. + * + * @param[in] paths A map with the current and new object identifiers. + */ void renameExpressions(const std::map& paths); + /** + * @brief Rename object identifiers in the registered expressions. + * + * @param[in] paths A map with the current and new object identifiers. + */ void renameObjectIdentifiers(const std::map& paths); - App::ObjectIdentifier canonicalPath(const App::ObjectIdentifier& p) const override; + /** + * @brief Create a canonical object identifier of the given object \a p. + * + * @param oid The object identifier from which we want a canonical path. + * @return The canonical object identifier. + */ + App::ObjectIdentifier canonicalPath(const App::ObjectIdentifier& oid) const override; + /// Get the number of expressions managed by this object. size_t numExpressions() const; /// signal called when an expression was changed @@ -210,14 +282,42 @@ private: using ExpressionMap = std::map; #endif + /** + * @brief Compute the evaluation order of the expressions. + * + * This method builds a graph for all expressions in the engine, and finds + * any circular dependencies. It also computes the internal evaluation + * order, in case properties depend on each other. + * + * @param[in] option Execution option, see ExecuteOption. + * + * @return A vector with the evaluation order of the properties and their + * dependencies in terms of object identifiers. + * + * @throws Base::RuntimeError if a circular dependency is detected. + */ std::vector computeEvaluationOrder(ExecuteOption option); + /** + * @brief Update graph structure with given path and expression. + * @param path Path + * @param expression Expression to query for dependencies + * @param nodes Map with nodes of graph, including dependencies of 'expression' + * @param revNodes Reverse map of the nodes, containing only the given paths, without dependencies. + * @param edges Edges in graph + */ void buildGraphStructures(const App::ObjectIdentifier& path, const std::shared_ptr expression, boost::unordered_map& nodes, boost::unordered_map& revNodes, std::vector& edges) const; - + /** + * @brief Build a graph of all expressions in \a exprs. + * @param exprs Expressions to use in graph + * @param revNodes Map from int[nodeid] to ObjectIndentifer. + * @param g Graph to update. May contain additional nodes than in revNodes, because of outside + * dependencies. + */ void buildGraph(const ExpressionMap& exprs, boost::unordered_map& revNodes, DiGraph& g, From 817c8e9dd3ce59311ab49c0f0c400a134cf1e327 Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Thu, 30 Oct 2025 11:57:25 +0100 Subject: [PATCH 4/7] Doc: Improve App::PropertyExpressionEngine docs --- src/App/PropertyExpressionEngine.h | 59 ++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/App/PropertyExpressionEngine.h b/src/App/PropertyExpressionEngine.h index 6b9d8b9b38..9ac5d78482 100644 --- a/src/App/PropertyExpressionEngine.h +++ b/src/App/PropertyExpressionEngine.h @@ -61,11 +61,46 @@ public: PropertyExpressionContainer(); ~PropertyExpressionContainer() override; + /** + * @brief Get the expressions map. + * + * This function returns a mapping from object identifier to expression. + * The object identifier specifies the property in the document object that + * the expression is bound to. + * + * @return The map of ObjectIdentifier to Expression pointers. + */ virtual std::map getExpressions() const = 0; + + /** + * @brief Set the expressions map. + * + * This function sets the mapping from object identifier to expression. + * The object identifier specifies the property in the document object that + * the expression is bound to. + * + * @param[in] exprs The new map of ObjectIdentifier to Expression pointers. + */ virtual void setExpressions(std::map&& exprs) = 0; protected: + /** + * @brief Handle document relabeling. + * + * Update the expressions in response to a document being relabeled. + * + * @param[in] doc The document that was relabeled. + */ virtual void onRelabeledDocument(const App::Document& doc) = 0; + + /** + * @brief Handle dynamic property renaming. + * + * Update the expressions in response to a dynamic property being renamed. + * + * @param[in] prop The property that was renamed. + * @param[in] oldName The old name of the property. + */ virtual void onRenameDynamicProperty(const App::Property& prop, const char* oldName) = 0; private: @@ -73,6 +108,18 @@ private: static void slotRenameDynamicProperty(const App::Property& prop, const char* oldName); }; + +/** + * @brief The class that manages expressions that target a property in a + * document object. + * @ingroup ExpressionFramework + * + * This class manages a set of expressions that are bound to properties in + * document objects. It provides functionality to evaluate the expressions, + * handle dependencies between expressions, and update the properties they + * are bound to. For a high-level overview of the %Expression framework see + * topic @ref ExpressionFramework "Expression Framework". + */ class AppExport PropertyExpressionEngine : public App::PropertyExpressionContainer, private App::AtomicPropertyChangeInterface @@ -98,7 +145,7 @@ public: std::shared_ptr expr)>; /** - * @brief The ExpressionInfo struct encapsulates an expression. + * @brief This struct encapsulates an expression. */ struct ExpressionInfo { @@ -127,8 +174,8 @@ public: void onRelabeledDocument(const App::Document& doc) override; void onRenameDynamicProperty(const App::Property& prop, const char* oldName) override; - void setValue() - {} // Dummy + /// Dummy setValue to satisfy a macro. + void setValue() {} Property* Copy() const override; @@ -211,7 +258,11 @@ public: */ bool depsAreTouched() const; - /* Expression validator */ + /** + * @brief Set an extra validator function. + * + * @param[in] f The validator function. + */ void setValidator(ValidatorFunc f) { validator = f; From 88c2e9122e2de4911993ecd0ea615c637e9cc96f Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Thu, 30 Oct 2025 13:31:47 +0100 Subject: [PATCH 5/7] Doc: Improve the Expression Framework topic --- src/App/core-app.dox | 81 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/src/App/core-app.dox b/src/App/core-app.dox index 672fbf243b..695b7658b3 100644 --- a/src/App/core-app.dox +++ b/src/App/core-app.dox @@ -227,7 +227,11 @@ * recomputed. Since properties may depend on other document objects, for * example because of the @ref App::DocumentObject::ExpressionEngine * "ExpressionEngine" property, these document objects have to be computed - * first. Managing the order of recomputation is managed by the @ref + * first. For more information on the role of expressions in recomputing + * document objects, see topic @ref SecExpressionsDocumentObjectRecompute + * "Expressions Framework". + * + * Managing the order of recomputation is managed by the @ref * App::Document "Document" which calls the @ref * App::DocumentObject::recompute() "recompute()" on the document object. To * signal that a document object needs to be recomputed, the document object @@ -251,7 +255,7 @@ * Vice versa, if a property of an object is changed, the InList indicates * which other objects depend on it and need to be recomputed. * - * So, as mentioned above, links define dependencies between document objects + * So, as mentioned above, links define dependencies between document objects. * These dependencies are recorded by means of setting the value of a @ref * App::PropertyLink "PropertyLink" (and similar properties) in a document * object `Obj` to a document object `Value`. Within @ref @@ -466,7 +470,78 @@ /** * @defgroup ExpressionFramework Expressions framework * @ingroup APP - * @brief The expression system allows users to write expressions and formulas that produce values + * @brief A system that allows users to write expressions and formulas that produce values. + * + * One of the differences between @ref DocumentObjectGroup "DocumentObjects" + * and @ref DocumentGroup "Documents" as @ref PropertyFramework + * "PropertyContainers" is that document objects support expressions whereas + * documents do not. %Document objects have a special hidden property called + * @ref App::DocumentObject::ExpressionEngine "ExpressionEngine" of type @ref + * App::PropertyExpressionEngine "PropertyExpressionEngine" that contains a + * mapping from @ref App::ObjectIdentifier "ObjectIdentifier" to @ref + * App::Expression "Expression". An object identifier defines the property + * inside the document object that will hold the result of the expression. We + * can also say that the expression is bound to the property that the object + * identifier identifies. + * + * Expressions can be as simple as `2 + 3`, but typically they reference other + * document objects and their properties, such as `Box001.Length` or even + * document objects in other documents such as `myDocument#Box001.Length`. + * + * Since expressions can reference other document objects, they effectively + * link to other document objects, potentially in other documents, and as such, + * a @ref App::PropertyExpressionEngine "PropertyExpressionEngine" inherits + * from the property external link container @ref App::PropertyXLinkContainer + * "PropertyXLinkContainer". + * + * @section SecExpressionsDocumentObjectRecompute The role of expressions in recomputing Documents + * + * For a high level overview of how document objects recompute, see topics @ref + * DependencyGraph "Document" and @ref SecDocumentObjectRecompute "Document + * Objects". On a recompute of a document object, the expressions in the + * expression engine are first evaluated by calling the @ref + * App::PropertyExpressionEngine::execute "PropertyExpressionEngine::execute()" + * function. This function obtains the values of the properties it references + * and evaluates the expression to a value that is stored in the property to + * which the expression is bound to. + * + * Now that the properties of the document objects have been updated according + * to the expressions, the document object can call @ref + * App::DocumentObject::execute "DocumentObject::execute()" to update its + * internal properties. + * + * After this execute of the document objects, the expression engine is + * executed again, but now only for the output properties ensuring that + * expressions that depend on properties of the document object itself that are + * set by @ref App::DocumentObject::execute "DocumentObject::execute()" are + * also updated. + * + * This process makes clear why it is important that there is a well-defined + * dependency graph and a topological order of document objects in terms of + * their dependencies as was discussed in topic @ref DependencyGraph + * "Document": If a document object is executed and references a property of + * another document object, we need to make sure that that document object has + * fully recomputed as we would compute a stale value of the property + * otherwise. Similarly, objects that refer to properties of a recently + * recomputed document object, need to be certain that the properties of this + * object have the latest value. As such, it is not possible to have a cyclic + * dependency between document objects as they would continuously update + * themselves. + * + * @section SecExpressionVisitors Expression Visitors + * + * Some actions require that expressions are updated. For example, when a document + * is relabeled, expressions that refer to the old name must be updated to + * refer to the new name. + * + * For this purpose, the visitor pattern is used. An expression visitor is derived + * from @ref App::ExpressionVisitor "ExpressionVisitor" and implements the + * virtual function @ref App::ExpressionVisitor::visit "visit()". This + * function is called for each node in the expression tree. + * + * There are two types of visitors: ones that gather information, for example a + * list of object identifiers in the expression, and ones that modify the + * expression, for example in case a document is relabeled. */ /** From 171a14f5427a8ef6ad020b39f7eae318e1345165 Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Sun, 9 Nov 2025 16:40:59 +0100 Subject: [PATCH 6/7] Doc: Improve documentation element mapping --- src/App/ComplexGeoData.h | 538 ++++++++++++++++++++-------- src/App/Document.h | 12 +- src/App/ElementMap.h | 205 ++++++++--- src/App/ElementNamingUtils.h | 28 +- src/App/IndexedName.h | 281 ++++++++++----- src/App/MappedElement.h | 52 ++- src/App/MappedName.h | 675 +++++++++++++++++++++++------------ 7 files changed, 1244 insertions(+), 547 deletions(-) diff --git a/src/App/ComplexGeoData.h b/src/App/ComplexGeoData.h index cc6446d344..ade8839d18 100644 --- a/src/App/ComplexGeoData.h +++ b/src/App/ComplexGeoData.h @@ -57,18 +57,21 @@ namespace Data { // struct MappedChildElements; + /// Option for App::GeoFeature::searchElementCache() enum class SearchOption { - /// Whether to compare shape geometry - CheckGeometry = 1, - SingleResult = 2, + CheckGeometry = 1, ///< Whether to compare shape geometry + SingleResult = 2, ///< Stop at first found result }; + typedef Base::Flags SearchOptions; -/** Segments - * Sub-element type of the ComplexGeoData type - * It is used to split an object in further sub-parts. +/** + * @brief A class for segments. + * + * A segment is a sub-element type of the ComplexGeoData type. It is used to + * split an object in further sub-parts. */ class AppExport Segment: public Base::BaseClass { @@ -76,200 +79,356 @@ class AppExport Segment: public Base::BaseClass public: ~Segment() override = default; + + /// Get the name of the segment. virtual std::string getName() const = 0; }; -enum ElementMapResetPolicy -{ - AllowNoMap, - ForceEmptyMap -}; - -/** ComplexGeoData Object +/** + * @brief A class for complex geometric data. + * @ingroup ElementMapping */ class AppExport ComplexGeoData: public Base::Persistence, public Base::Handled { TYPESYSTEM_HEADER_WITH_OVERRIDE(); // NOLINT public: + /** + * @brief A line as a facet boundary in a 3D mesh. + * + * The line is represented by two point indices. + */ struct Line { uint32_t I1; uint32_t I2; }; + + /** + * @brief A triangular facet in a 3D mesh. + * + * The facet is represented by three point indices. + */ struct Facet { uint32_t I1; uint32_t I2; uint32_t I3; }; + + /** + * @brief A domain in a 3D mesh. + * + * A domain consists of a list of points and a list of facets where the + * indices of a facet index into the points list. + */ struct Domain { std::vector points; std::vector facets; }; - /// Constructor ComplexGeoData(); - /// Destructor ~ComplexGeoData() override = default; - /** @name Sub-element management */ - //@{ - /** Sub type list - * List of different sub-element types - * its NOT a list of the sub-elements itself + /** + * @name Sub-element management + * @{ + */ + + /** + * @brief Get a list of sub-element types. + * + * This list does not contain the sub-elements themselves. */ virtual std::vector getElementTypes() const = 0; + + /** + * @brief Get the number of sub-elements of a given type. + * + * @param[in] Type The type of sub-element. + * @return The number of sub-elements of the given type. + */ virtual unsigned long countSubElements(const char* Type) const = 0; - /// Returns a generic element type and index. The determined element type isn't - /// necessarily supported by this geometry. + + /** + * @brief Get the type and index from a combined name. + * + * An example is "Edge12", which would return the type "Edge" and the + * index 12. + * + * @param[in] Name The combined name of the sub-element. + * @returns A pair of a generic element type and index. + * + * @note The determined element type isn't necessarily supported by this + * geometry. + */ static std::pair getTypeAndIndex(const char* Name); - /// get the sub-element by type and number + + /// Get the sub-element by type and number. virtual Segment* getSubElement(const char* Type, unsigned long) const = 0; - /// get sub-element by combined name + + /// Get sub-element by combined name virtual Segment* getSubElementByName(const char* Name) const; - /** Get lines from segment */ - virtual void getLinesFromSubElement(const Segment*, + + /** + * @brief Get the lines from a segment. + * + * @param[in] segment The segment to get the lines from. + * + * @param[in,out] Points The list of points used by the lines. + * @param[in,out] lines The list of lines retrieved from the segment. + */ + virtual void getLinesFromSubElement(const Segment* segment, std::vector& Points, std::vector& lines) const; - /** Get faces from segment */ - virtual void getFacesFromSubElement(const Segment*, + + /** + * @brief Get the faces from a segment. + * + * @param[in] segment The segment to get the faces from. + * @param[in,out] Points The list of points used by the faces. + * @param[in,out] PointNormals The list of point normals used by the faces. + * @param[in,out] faces The list of faces retrieved from the segment. + */ + virtual void getFacesFromSubElement(const Segment* segment, std::vector& Points, std::vector& PointNormals, std::vector& faces) const; - //@} + /// @} - /** @name Placement control */ - //@{ - /** Applies an additional transformation to the current transformation. */ + /** + * @name Placement control + * + * @{ + */ + + /// Apply an additional transformation to the current transformation. void applyTransform(const Base::Matrix4D& rclTrf); - /** Applies an additional translation to the current transformation. */ + + /// Apply an additional translation to the current transformation. void applyTranslation(const Base::Vector3d&); - /** Applies an additional rotation to the current transformation. */ + + /// Applies an additional rotation to the current transformation. void applyRotation(const Base::Rotation&); - /** Override the current transformation with a placement - * using the setTransform() method. + + /** + * @brief Override the current transformation with a placement. + * + * Override the current transformation with a placement using the + * setTransform() method. + * + * @param[in] rclPlacement The new placement to set. */ void setPlacement(const Base::Placement& rclPlacement); - /** Return the current transformation as placement using - * getTransform(). + + /** + * @brief Get the current transformation as placement. + * + * @return The current transformation as placement using getTransform(). */ Base::Placement getPlacement() const; - /** Override the current transformation with the new one. - * This method has to be handled by the child classes. - * the actual placement and matrix is not part of this class. + + /** + * @brief Override the current transformation. + * + * Override the current transformation with the new one. This method has + * to be handled by the child classes. the actual placement and matrix is + * not part of this class. + * + * @param[in] rclTrf The new transformation matrix to set. */ + virtual void setTransform(const Base::Matrix4D& rclTrf) = 0; - /** Return the current matrix - * This method has to be handled by the child classes. - * the actual placement and matrix is not part of this class. + + /** + * @brief Get the current transformation matrix. + * + * This method has to be handled by the child classes. The actual + * placement and matrix is not part of this class. */ virtual Base::Matrix4D getTransform() const = 0; - //@} + /// @} - /** @name Modification */ - //@{ - /// Applies a transformation on the real geometric data type + /** + *@name Modification + * + * @{ + */ + + /** + * @brief Applies a transformation on the real geometric data type. + * + * @param[in] rclMat The transformation matrix to apply. + */ virtual void transformGeometry(const Base::Matrix4D& rclMat) = 0; - //@} + /// @} - /** @name Getting basic geometric entities */ - //@{ - /// Get the standard accuracy to be used with getPoints, getLines or getFaces + /** + * @name Getting basic geometric entities + * + * @{ + */ + + /** + * @brief Get the standard accuracy for meshing. + * + * Get the standard accuracy to be used with getPoints(), getLines() or + * getFaces(). + * + * @return The standard accuracy. + */ virtual double getAccuracy() const; + /// Get the bound box virtual Base::BoundBox3d getBoundBox() const = 0; - /** Get point from line object intersection */ + + /** + * @brief Get point from line object intersection. + * + * @param[in] base The base point of the line. + * @param[in] dir The direction of the line. + * + * @return The intersection point. + */ virtual Base::Vector3d getPointFromLineIntersection(const Base::Vector3f& base, const Base::Vector3f& dir) const; - /** Get points from object with given accuracy */ + + /** + * @brief Get points from object with given accuracy. + * + * @param[in,out] Points The list of points retrieved from the object. + * @param[in,out] Normals The list of normals associated with faces + * retrieved from the object. If there are no faces, then this list will + * be empty. + * @param[in] Accuracy The accuracy to use when retrieving the points. + * @param[in] flags Additional flags for point retrieval. + */ virtual void getPoints(std::vector& Points, std::vector& Normals, double Accuracy, uint16_t flags = 0) const; - /** Get lines from object with given accuracy */ + + /** + * @brief Get lines from object with given accuracy + * + * @param[in,out] Points The list of points retrieved from the object. + * @param[in,out] lines The list of lines retrieved from the object. + * @param[in] Accuracy The accuracy to use when retrieving the lines. + * @param[in] flags Additional flags for line retrieval. + */ virtual void getLines(std::vector& Points, std::vector& lines, double Accuracy, uint16_t flags = 0) const; - /** Get faces from object with given accuracy */ + + /** + * @brief Get faces from object with given accuracy. + * + * @param[in,out] Points The list of points retrieved from the object. + * @param[in,out] faces The list of faces retrieved from the object. + * @param[in] Accuracy The accuracy to use when retrieving the faces. + * @param[in] flags Additional flags for face retrieval. + */ virtual void getFaces(std::vector& Points, std::vector& faces, double Accuracy, uint16_t flags = 0) const; - /** Get the center of gravity - * If this method is implemented then true is returned and the center of gravity. - * The default implementation only returns false. + + /** + * @brief Get the center of gravity. + * + * @param[out] center The center of gravity. + * + * @return True if this method is implemented. The default implementation returns false. */ virtual bool getCenterOfGravity(Base::Vector3d& center) const; - virtual std::optional centerOfGravity() const; - //@} + /** + * @brief Get the center of gravity. + * + * @return The center of gravity if available. + */ + virtual std::optional centerOfGravity() const; + /// @} + + /// Get the element map prefix. static const std::string& elementMapPrefix(); - /** @name Element name mapping */ - //@{ - - /** Get element indexed name + /** + * @name Element name mapping * - * @param name: the input name - * @param sid: optional output of and App::StringID involved forming this mapped name + * @{ + */ + + /** + * @brief Get the element's indexed name. + * + * @param[in] name The mapped name. + * + * @param[out] sid Optional output of and App::StringID involved forming + * this mapped name. * * @return Returns an indexed name. */ IndexedName getIndexedName(const MappedName& name, ElementIDRefs* sid = nullptr) const; - /** Get element mapped name + /** + * @brief Get the element's mapped name. * - * @param name: the input name - * @param allowUnmapped: If the queried element is not mapped, then return - * an empty name if \c allowUnmapped is false, or - * else, return the indexed name. - * @param sid: optional output of and App::StringID involved forming this mapped name - * @return Returns the mapped name. + * @param[in] element The indexed name name of the element. + * @param[in] allowUnmapped If the queried element is not mapped, then + * return an empty name if @p allowUnmapped is false, or else, return the + * indexed name. + * @param sid Optional output of and App::StringID involved forming this + * mapped name. + * + * @return The mapped name. */ MappedName getMappedName(const IndexedName& element, bool allowUnmapped = false, ElementIDRefs* sid = nullptr) const; - /** Return a pair of indexed name and mapped name - * - * @param name: the input name. - * @param sid: optional output of any App::StringID involved in forming - * this mapped name - * @param copy: if true, copy the name string, or else use it as constant - * string, and caller must make sure the memory is not freed. - * - * @return Returns the MappedElement which contains both the indexed and - * mapped name. + /** + * @brief Get a pair of an indexed and mapped name. * * This function guesses whether the input name is an indexed name or - * mapped, and perform a lookup and return the names found. If the input + * mapped one, performs a lookup, and returns the names found. If the input * name contains only alphabets and underscore followed by optional digits, - * it will be treated as indexed name. Or else, it will be treated as + * it will be treated as indexed name. Otherwise, it will be treated as a * mapped name. + * + * @param[in] name The input name. + * @param[out] sid Optional output of any App::StringID involved in forming + * this mapped name. + * @param[in] copy If true, copy the name string, or else use it as + * constant string, and caller must make sure the memory is not freed. + * + * @return The MappedElement that contains both the indexed and mapped + * name. */ MappedElement getElementName(const char* name, ElementIDRefs* sid = nullptr, bool copy = false) const; - /** Add a sub-element name mapping. - * - * @param element: the original \c Type + \c Index element name - * @param name: the mapped sub-element name. May or may not start with - * elementMapPrefix(). - * @param sid: in case you use a hasher to hash the element name, pass in - * the string id reference using this parameter. You can have more than one - * string id associated with the same name. - * @param overwrite: if true, it will overwrite existing names - * - * @return Returns the stored mapped element name. + /** + * @brief Add a sub-element name mapping. * * An element can have multiple mapped names. However, a name can only be * mapped to one element * - * Note: the original proc was in the context of ComplexGeoData, which provided `Tag` access, - * now you must pass in `long masterTag` explicitly. + * @param[in] element The original @c Type + @c Index element name. + * @param[in] name The mapped sub-element name. It may or may not start + * with elementMapPrefix(). + * @param[in] masterTag The master tag of the element. + * @param[in] sid In case you use a hasher to hash the element name, pass + * in the string id reference using this parameter. You can have more than + * one string id associated with the same name. + * @param[in] overwrite If true, it will overwrite existing names. + * + * @return The stored mapped element name. + * + * @note The original function was in the context of ComplexGeoData, which + * provided `Tag` access, now you must pass in `long masterTag` explicitly. */ MappedName setElementName(const IndexedName& element, const MappedName& name, @@ -280,30 +439,42 @@ public: return _elementMap->setElementName(element, name, masterTag, sid, overwrite); } + /// Check if there is an element map. bool hasElementMap() const { return _elementMap != nullptr; } - /** Get mapped element names + /** + * @brief Get mapped element names. * - * @param element: original element name with \c Type + \c Index - * @param needUnmapped: if true, return the original element name if no - * mapping is found + * @param[in] element The original element name with @c Type + @c Index. + * @param[in] needUnmapped If true, return the original element name if no + * mapping is found. * - * @return a list of mapped names of the give element along with their - * associated string ID references + * @return A list of mapped names of the give element along with their + * associated string ID references. */ std::vector> getElementMappedNames(const IndexedName& element, bool needUnmapped = false) const; - /// Hash the child element map postfixes to shorten element name from hierarchical maps + /** + * @brief Hash the child element map postfixes. + * + * The hashing is done to shorten element names from hierarchical maps. + */ void hashChildMaps(); - /// Check if there is child element map + /// Check if there is child element map. bool hasChildElementMap() const; - /// Append the Tag (if and only if it is non zero) into the element map + /** + * @brief Append the Tag (if and only if it is non zero) into the element map. + * + * @param[in] tag The master tag to append. + * @param[in] hasher The string hasher to use. + * @param[in] postfix An optional postfix to append after the tag. + */ virtual void reTagElementMap(long tag, App::StringHasherRef hasher, const char* postfix = nullptr) { @@ -312,7 +483,17 @@ public: (void)postfix; } - // NOTE: getElementHistory is now in ElementMap + /** + * @brief Get the history of an element name. + * + * @param[in] name The mapped element name to query. + * @param[out] original Optional output parameter to store the original + * element name. + * @param[out] history Optional output parameter to store the history of + * element names. + * + * @note This function is now in ElementMap. + */ long getElementHistory(const MappedName& name, MappedName* original = nullptr, std::vector* history = nullptr) const @@ -323,49 +504,68 @@ public: return 0; }; + /// Set the mapped child elements. void setMappedChildElements(const std::vector& children); + + /// Get the mapped child elements. std::vector getMappedChildElements() const; + /// Get the element type from a mapped name. char elementType(const Data::MappedName&) const; + + /// Get the element type from an indexed name. char elementType(const Data::IndexedName&) const; + + /// Get the element type from a raw name. char elementType(const char* name) const; - /** Reset/swap the element map + /** + * @brief Reset/swap the element map. * - * @param elementMap: optional new element map + * @param[in] elementMap: optional new element map. * - * @return Returns the existing element map. + * @return The existing element map. */ virtual ElementMapPtr resetElementMap(ElementMapPtr elementMap = ElementMapPtr()); - /// Get the entire element map + /// Get the entire element map. std::vector getElementMap() const; - /// Set the entire element map + /// Set the entire element map. void setElementMap(const std::vector& elements); - /// Get the current element map size + /// Get the current element map size. size_t getElementMapSize(bool flush = true) const; - /// Return the higher level element names of the given element + /** + * @brief Get the higher level element names of the given element. + * + * @param name: the input element name. + * @param silent: if true, suppress throwing exceptions. + */ virtual std::vector getHigherElements(const char* name, bool silent = false) const; - /// Return the current element map version + /// Get the current element map version. virtual std::string getElementMapVersion() const; - /// Return true to signal element map version change + /// Check the element map version. virtual bool checkElementMapVersion(const char* ver) const; - /// Check if the given sub-name only contains an element name + /// Check if the given sub-name only contains an element name. static bool isElementName(const char* subName) { return (subName != nullptr) && (*subName != 0) && findElementName(subName) == subName; } - /** Iterate through the history of the give element name with a given callback + /** + * @brief Iterate through the history given an element name. + * + * Iterate through the history of the given element name with a given + * callback. + * + * @param[in] name: the input element name + * @param[in] cb: trace callback function. * - * @param name: the input element name - * @param cb: trace callback with call signature. * @sa TraceCallback */ void traceElement(const MappedName& name, TraceCallback cb) const @@ -373,42 +573,62 @@ public: _elementMap->traceElement(name, Tag, std::move(cb)); } - /** Flush an internal buffering for element mapping */ + /// Flush internal buffers for element mapping. virtual void flushElementMap() const; - //@} + /// @} + + /** + * @name Save/restore + * + * @{ + */ - /** @name Save/restore */ - //@{ void Save(Base::Writer& writer) const override; void Restore(Base::XMLReader& reader) override; void SaveDocFile(Base::Writer& writer) const override; void RestoreDocFile(Base::Reader& reader) override; unsigned int getMemSize() const override; + + /// Set the filename for persistence. void setPersistenceFileName(const char* name) const; + + /// Called before saving. virtual void beforeSave() const; + + /// Check if restore has failed. bool isRestoreFailed() const { return _restoreFailed; } + + /// Reset the restore failure flag. void resetRestoreFailure() const { _restoreFailed = true; } - //@} + /// @} /** - * Debugging method to dump an entire element map in human readable form to a stream - * @param stream + * @brief Dump the entire element map. + * + * Debugging method to dump an entire element map in human readable form to a stream. + * + * @param[in,out] stream The output stream. */ void dumpElementMap(std::ostream& stream) const; + /** - * Debugging method to dump an entire element map in human readable form into a string - * @return The string + * @brief Dump the entire element map to a string. + * + * Debugging method to dump an entire element map in human readable form + * into a string. + * + * @return The string with the element map. */ const std::string dumpElementMap() const; protected: - /// from local to outside + /// Transform the point from local to outside. inline Base::Vector3d transformPointToOutside(const Base::Vector3f& vec) const { // clang-format off @@ -417,7 +637,8 @@ protected: static_cast(vec.z)); // clang-format on } - /// from local to outside + + /// Transform the points from local to outside. template inline std::vector transformPointsToOutside(const std::vector& input) const { @@ -435,6 +656,8 @@ protected: return output; // clang-format on } + + /// Transform the vector from local to outside. inline Base::Vector3d transformVectorToOutside(const Base::Vector3f& vec) const { // clang-format off @@ -445,6 +668,8 @@ protected: static_cast(vec.z)); // clang-format on } + + /// Transform the vectors from local to outside. template std::vector transformVectorsToOutside(const std::vector& input) const { @@ -463,7 +688,8 @@ protected: return output; // clang-format on } - /// from local to inside + + /// Transform the point from local to inside. inline Base::Vector3f transformPointToInside(const Base::Vector3d& vec) const { Base::Matrix4D tmpM(getTransform()); @@ -475,44 +701,48 @@ protected: } public: + /** + * @brief The master tag. + * + * The master tag is used to identify the shape to its owner document + * object. It should be unique within the document. A tag of zero meanss + * that automatic element mapping is disabled. + */ mutable long Tag {0}; /// String hasher for element name shortening mutable App::StringHasherRef Hasher; protected: + /** + * @brief Restore the element map from a stream. + * + * @param[in,out] stream The input stream. + * @param[in] count The number of items to restore. + */ void restoreStream(std::istream& stream, std::size_t count); + + /** + * @brief Read the elements from an XML reader. + * + * @param[in,out] reader The XML reader. + * @param[in] count The number of elements to read. + */ void readElements(Base::XMLReader& reader, size_t count); - /// from local to outside - inline Base::Vector3d transformToOutside(const Base::Vector3f& vec) const - { - // clang-format off - return getTransform() * Base::Vector3d(static_cast(vec.x), - static_cast(vec.y), - static_cast(vec.z)); - // clang-format on - } - /// from local to inside - inline Base::Vector3f transformToInside(const Base::Vector3d& vec) const - { - Base::Matrix4D tmpM(getTransform()); - tmpM.inverse(); - Base::Vector3d tmp = tmpM * vec; - return Base::Vector3f(static_cast(tmp.x), - static_cast(tmp.y), - static_cast(tmp.z)); - } - protected: + /// Get the element map. ElementMapPtr elementMap(bool flush = true) const; + /// Ensure there is an element map. ElementMapPtr ensureElementMap(bool flush = true); private: ElementMapPtr _elementMap; protected: + /// The persistence file name. mutable std::string _persistenceName; + /// Flag to indicate restore failure. mutable bool _restoreFailed = false; }; diff --git a/src/App/Document.h b/src/App/Document.h index 7a647d2b50..7347ff7513 100644 --- a/src/App/Document.h +++ b/src/App/Document.h @@ -465,14 +465,14 @@ public: /** * @brief Add an object of a given type to the document. * - * Add an object of of a given type with @p pObjectName that should be - * ASCII to this document and set it active. Unicode names are set through - * the Label property. + * Add an object of a given type with @p pObjectName that should be ASCII + * to this document and set it active. Unicode names are set through the + * Label property. * * @tparam T The type of created object. - * @param[in] pObjectName if `nullptr` generate a new unique name based on @p - * T, otherwise use this name. - * @param[in] isNew if `false don't call the DocumentObject::setupObject() + * @param[in] pObjectName if `nullptr` generate a new unique name based on + * @p T, otherwise use this name. + * @param[in] isNew if `false` don't call the DocumentObject::setupObject() * callback (default * is true) * @param[in] viewType override object's view provider name * @param[in] isPartial indicate if this object is meant to be partially loaded diff --git a/src/App/ElementMap.h b/src/App/ElementMap.h index 29c7c2ab9a..f1a3f0154f 100644 --- a/src/App/ElementMap.h +++ b/src/App/ElementMap.h @@ -45,80 +45,108 @@ namespace Data class ElementMap; using ElementMapPtr = std::shared_ptr; -/** Element trace callback +/** + * @brief The element trace callback. * * The callback has the following call signature * (const std::string &name, size_t offset, long encodedTag, long tag) -> bool * - * @param name: the current element name. - * @param offset: the offset skipping the encoded element name for the next iteration. - * @param encodedTag: the tag encoded inside the current element, which is usually the tag - * of the previous step in the shape history. - * @param tag: the tag of the current shape element. + * @param[in] name The current element name. + * + * @param[in] offset The offset skipping the encoded element name for the next + * iteration. + * + * @param[in] encodedTag The tag encoded inside the current element, which is + * usually the tag of the previous step in the shape history. + * + * @param[in] tag The tag of the current shape element. * * @sa traceElement() */ typedef std::function TraceCallback; -/* This class provides for ComplexGeoData's ability to provide proper naming. +/** + * @brief A class to manage element name mapping. + * @ingroup ElementMapping + * + * This class provides for ComplexGeoData's ability to provide proper naming. * Specifically, ComplexGeoData uses this class for it's `_id` property. * Most of the operations work with the `indexedNames` and `mappedNames` maps. - * `indexedNames` maps a string to both a name queue and children. - * each of those children store an IndexedName, offset details, postfix, ids, and - * possibly a recursive elementmap - * `mappedNames` maps a MappedName to a specific IndexedName. + * + * - `indexedNames` maps a string to both a name queue and children. Each of + * those children store an IndexedName, offset details, postfix, ids, and + * possibly a recursive elementmap. + * - `mappedNames` maps a MappedName to a specific IndexedName. */ class AppExport ElementMap : public std::enable_shared_from_this // TODO can remove shared_from_this? { public: - /** Default constructor: hooks internal functions to \c signalSaveDocument and - * \c signalStartRestoreDocument. This is related to the save and restore process - * of the map. + /** + * @brief Construct an element map. + * + * Default constructor: hooks internal functions to \c signalSaveDocument + * and \c signalStartRestoreDocument. This is related to the save and + * restore process of the map. */ ElementMap(); - /** Ensures that naming is properly assigned. It then marks as "used" all the StringID - * that are used to make up this particular map and are stored in the hasherRef passed - * as a parameter. Finally do this recursively for all childEelementMaps as well. + /** + * @brief Prepare this map for saving. * - * @param hasherRef where all the StringID needed to build the map are stored. + * Ensures that naming is properly assigned. It then marks as "used" all + * the @ref App::StringID "StringIDs" that are used to make up this + * particular map and are stored in the hasherRef passed as a + * parameter. Finally do this recursively for all childEelementMaps as + * well. + * + * @param hasherRef Where all the @ref App::StringID "StringIDs" that are + * needed to build the map are stored. */ // FIXME this should be made part of \c save, to achieve symmetry with the restore method void beforeSave(const ::App::StringHasherRef& hasherRef) const; - /** Serialize this map. Calls \c collectChildMaps to get \c childMapSet and - * \c postfixMap, then calls the other (private) save function with those parameters. - * @param stream: serialized stream + /** + * @brief Serialize this map. + * + * Serialize this map. Calls @c collectChildMaps to get @c childMapSet and + * @c postfixMap, then calls the other (private) save function with those + * parameters. + * + * @param[in,out] stream The stream to serialize to. */ void save(std::ostream& stream) const; - /** Deserialize and restore this map. This function restores \c childMaps and - * \c postfixes from the stream, then calls the other (private) restore function with those - * parameters. - * @param hasherRef: where all the StringIDs are stored - * @param stream: stream to deserialize + /** + * @brief Deserialize and restore this map. + * + * This function restores @c childMaps and @c postfixes from the stream, + * then calls the other (private) restore function with those parameters. + * + * @param[in] hasherRef Where all the StringIDs are stored. + * @param[in,out] stream The stream to deserialize from. */ ElementMapPtr restore(::App::StringHasherRef hasherRef, std::istream& stream); - - /** Add a sub-element name mapping. - * - * @param element: the original \c Type + \c Index element name - * @param name: the mapped sub-element name. May or may not start with - * elementMapPrefix(). - * @param sid: in case you use a hasher to hash the element name, pass in - * the string id reference using this parameter. You can have more than one - * string id associated with the same name. - * @param overwrite: if true, it will overwrite existing names - * - * @return Returns the stored mapped element name. + /** + * @brief Add a sub-element name mapping. * * An element can have multiple mapped names. However, a name can only be * mapped to one element * - * Note: the original proc was in the context of ComplexGeoData, which provided `Tag` access, - * now you must pass in `long masterTag` explicitly. + * @param[in] element The original @c Type + @c Index element name. + * @param[in] name The mapped sub-element name. May or may not start with + * elementMapPrefix(). + * @param[in] masterTag The master tag of the element. + * @param[in] sid In case you use a hasher to hash the element name, pass + * in the string id reference using this parameter. You can have more than + * one string id associated with the same name. + * @param[in] overwrite If true, it will overwrite existing names. + * + * @return The stored mapped element name. + * + * @note The original function was in the context of ComplexGeoData, which + * provided `Tag` access, now you must pass in `long masterTag` explicitly. */ MappedName setElementName(const IndexedName& element, const MappedName& name, @@ -126,12 +154,22 @@ public: const ElementIDRefs* sid = nullptr, bool overwrite = false); - /* Generates a new MappedName from the current details. + /** + * @brief Generates a new MappedName from the current details. * * The result is streamed to `ss` and stored in `name`. * - * Note: the original proc was in the context of ComplexGeoData, which provided `Tag` access, - * now you must pass in `long masterTag` explicitly. + * @param[in] element_type The element type character. + * @param[in,out] name The mapped name to encode. + * @param[in,out] ss The output stream to write the encoded name to. + * @param[in,out] sids The string id references that make up the name. + * @param[in] masterTag The master tag of the element. + * @param[in] postfix Optional postfix to append to the name. + * @param[in] tag Optional tag to encode in the name. + * @param[in] forceTag If true, encode the tag. + * + * @note The original function was in the context of ComplexGeoData, which + * provided `Tag` access, now you must pass in `long masterTag` explicitly. */ void encodeElementName(char element_type, MappedName& name, @@ -142,20 +180,47 @@ public: long tag = 0, bool forceTag = false) const; - /// Remove \c name from the map + /// Remove @p name from the map. void erase(const MappedName& name); - /// Remove \c idx and all the MappedNames associated with it + /// Remove @p idx and all the MappedNames associated with it. void erase(const IndexedName& idx); + /// Get the size of the map. unsigned long size() const; + /// Check if the map is empty. bool empty() const; + /** + * @brief Find the IndexedName associated with the given MappedName. + * + * @param[in] name The mapped name to search for. + * @param[in,out] sids Optional pointer to store the StringID references that + * make up the name. + * + * @return The IndexedName associated with the given MappedName, or an + * empty IndexedName if not found. + */ IndexedName find(const MappedName& name, ElementIDRefs* sids = nullptr) const; + /** + * @brief Find the MappedName associated with the given IndexedName. + * + * @param[in] idx The indexed name to search for. + * @param[in,out] sids Optional pointer to store the StringID references that + * make up the name. + */ MappedName find(const IndexedName& idx, ElementIDRefs* sids = nullptr) const; + /** + * @brief Find all MappedNames associated with the given IndexedName. + * + * @param[in] idx The indexed name to search for. + * + * @return A vector of pairs of MappedName and their corresponding + * ElementIDRefs associated with the given IndexedName. + */ std::vector> findAll(const IndexedName& idx) const; // prefix searching is disabled, as TopoShape::getRelatedElement() is @@ -166,16 +231,23 @@ public: std::vector findAllStartsWith(const char *prefix) const; #endif + /// Check if there are any child element maps. bool hasChildElementMap() const; - /* Ensures that for each IndexedName mapped to IndexedElements, that - * each child is properly hashed (cached). + /** + * @brief Hash the child maps. * - * Note: the original proc was in the context of ComplexGeoData, which provided `Tag` access, - * now you must pass in `long masterTag` explicitly. + * Ensures that for each IndexedName mapped to IndexedElements, that each + * child is properly hashed (cached). + * + * @param[in] masterTag The master tag of the element. + * + * @note The original function was in the context of ComplexGeoData, which + * provided `Tag` access, now you must pass in `long masterTag` explicitly. */ void hashChildMaps(long masterTag); + /// A struct to represent mapped child elements. struct AppExport MappedChildElements { IndexedName indexedName; @@ -189,23 +261,48 @@ public: // prefix() has been moved to ElementNamingUtils.h }; - /* Note: the original addChildElements passed `ComplexGeoData& master` for getting the `Tag`, - * now it just passes `long masterTag`.*/ + /** + * @brief Add child elements to the map. + * + * @param[in] masterTag The master tag of the element. + * @param[in] children The vector of child elements to add. + * + * @note The original addChildElements passed `ComplexGeoData& master` for + * getting the `Tag`, now it just passes `long masterTag`. + */ void addChildElements(long masterTag, const std::vector& children); + /// Get the child elements. std::vector getChildElements() const; + /// Get all mapped elements. std::vector getAll() const; + /** + * @brief Get the history of the given element name. + * + * @param[in] name The input element name. + * @param[in] masterTag The master tag of the element. + * @param[out] original Optional output parameter to store the original element name. + * @param[out] history Optional output parameter to store the history of element names. + * + * @return The tag associated with the original element name. + */ long getElementHistory(const MappedName& name, long masterTag, MappedName* original = nullptr, std::vector* history = nullptr) const; - /** Iterate through the history of the give element name with a given callback + /** + * @brief Iterate through the history of the given element name. + * + * Iterate through the history of the give element name with a given + * callback + * + * @param[in] name The input element name. + * @param[in] masterTag The master tag of the element. + * @param[in] cb The trace callback. * - * @param name: the input element name - * @param cb: trace callback with call signature. * @sa TraceCallback */ void traceElement(const MappedName& name, long masterTag, TraceCallback cb) const; diff --git a/src/App/ElementNamingUtils.h b/src/App/ElementNamingUtils.h index db57fd0884..c0484c55b6 100644 --- a/src/App/ElementNamingUtils.h +++ b/src/App/ElementNamingUtils.h @@ -10,9 +10,7 @@ namespace App { -/** Return type for lookups of new and old style sub-element names - * - */ +/// Return type for lookups of new and old style sub-element names struct ElementNamePair { std::string newName; @@ -42,8 +40,17 @@ struct ElementNamePair namespace Data { +/** + * @name Element name constants + * @ingroup ElementMapping + * @anchor ElementNameConstants + * + * @{ + */ + /// Special prefix to mark the beginning of a mapped sub-element name constexpr const char* ELEMENT_MAP_PREFIX = ";"; +/// The size of the element map prefix constexpr size_t ELEMENT_MAP_PREFIX_SIZE = 1; /// Special prefix to mark a missing element @@ -52,27 +59,40 @@ constexpr const char* MISSING_PREFIX = "?"; // IMPORTANT: For all the constants below, the semicolon ";" // at the start is ELEMENT_MAP_PREFIX +/// Prefix to mark child elements. constexpr const char* MAPPED_CHILD_ELEMENTS_PREFIX = ";:R"; /// Special postfix to mark the following tag constexpr const char* POSTFIX_TAG = ";:H"; +/// The size of the postfix tag constexpr size_t POSTFIX_TAG_SIZE = 3; +/// Postfix to mark a decimal tag. constexpr const char* POSTFIX_DECIMAL_TAG = ";:T"; +/// Postfix to mark an external tag. constexpr const char* POSTFIX_EXTERNAL_TAG = ";:X"; +/// Postfix to mark a child element. constexpr const char* POSTFIX_CHILD = ";:C"; /// Special postfix to mark the index of an array element constexpr const char* POSTFIX_INDEX = ";:I"; +/// Postfix to mark an element higher in the hierarcy. constexpr const char* POSTFIX_UPPER = ";:U"; +/// Postfix to mark an element lower in the hierarcy. constexpr const char* POSTFIX_LOWER = ";:L"; +/// Postfix to mark an element as being modified. constexpr const char* POSTFIX_MOD = ";:M"; +/// Postfix to mark an element as being generated. constexpr const char* POSTFIX_GEN = ";:G"; +/// Postfix to mark an element as being modified and generated. constexpr const char* POSTFIX_MODGEN = ";:MG"; +/// Postfix to mark a duplicate element. constexpr const char* POSTFIX_DUPLICATE = ";D"; - +/// Label to use for element index in element mapping. constexpr const char* ELEMENT_MAP_INDEX = "_"; +/// @} + /// Check if a subname contains missing element AppExport bool hasMissingElement(const char *subname); diff --git a/src/App/IndexedName.h b/src/App/IndexedName.h index 51aa4b30cc..b7af45260d 100644 --- a/src/App/IndexedName.h +++ b/src/App/IndexedName.h @@ -40,30 +40,44 @@ namespace Data { -/// The IndexedName class provides a very memory-efficient data structure to hold a name and an -/// index value, and to perform various comparisons and validations of those values. The name must -/// only consist of upper- and lower-case ASCII characters and the underscore ('_') character. The -/// index must be a positive integer. The string representation of this IndexedName is the name -/// followed by the index, with no spaces between: an IndexedName may be constructed from this -/// string. For example "EDGE1" or "FACE345" might be the names of elements that use an IndexedName. -/// If there is then an "EDGE2", only a pointer to the original stored name "EDGE" is retained. -/// -/// The memory efficiency of the class comes from reusing the same character storage for names that -/// match, while retaining their differing indices. This is achieved by either using user-provided -/// const char * names (provided as a list of typeNames and presumed to never be deallocated), or by -/// maintaining an internal list of names that have been used before, and can be reused later. +/** + * @brief A data structure to hold a name and an index value. + * @ingroup ElementMapping + * + * The IndexedName class provides a very memory-efficient data structure to hold a name and an + * index value, and to perform various comparisons and validations of those values. The name must + * only consist of upper- and lower-case ASCII characters and the underscore ('_') character. The + * index must be a positive integer. The string representation of this IndexedName is the name + * followed by the index, with no spaces between: an IndexedName may be constructed from this + * string. For example "Edge1" or "Face345" might be the names of elements that use an IndexedName. + * If there is then an "Edge2", only a pointer to the original stored name "Edge" is retained. + * + * The memory efficiency of the class comes from reusing the same character storage for names that + * match, while retaining their differing indices. This is achieved by either using user-provided + * const char * names (provided as a list of typeNames and presumed to never be deallocated), or by + * maintaining an internal list of names that have been used before, and can be reused later. + */ class AppExport IndexedName { public: - /// Construct from a name and an optional index. If the name contains an index it is read, but - /// is used as the index *only* if _index parameter is unset. If the _index parameter is given - /// it overrides any trailing integer in the name. Index must be positive, and name must contain - /// only ASCII letters and the underscore character. If these conditions are not met, name is - /// set to the empty string, and isNull() will return true. - /// - /// \param name The new name - ASCII letters and underscores only, with optional integer suffix. - /// This memory will be copied into a new internal storage location and need not be persistent. - /// \param _index The new index - if provided, it overrides any suffix provided by name + /** + * @brief Construct an indexed name from a name and optional index. + * + * Construct an indexed name from a name and an optional index. If the name + * contains an index, it is read, but is used as the index *only* if @p + * _index is unset. If the @p _index is given, it overrides any trailing + * integer in the name. The index must be positive, and the name must + * contain only ASCII letters and the underscore character. If these + * conditions are not met, the name is set to the empty string, and + * isNull() will return true. + * + * @param[in] name The new name - ASCII letters and underscores only, with + * optional integer suffix. This memory will be copied into a new internal + * storage location and need not be persistent. + * + * @param[in] _index The new index. If provided, it overrides any suffix + * provided by name. + */ explicit IndexedName(const char* name = nullptr, int _index = 0) : index(0) { @@ -79,20 +93,31 @@ public: } } - /// Create an indexed name that is restricted to a list of preset type names. If it appears in - /// that list, only a pointer to the character storage in the list is retained: the memory - /// locations pointed at by the list must never be destroyed once they have been used to create - /// names. If allowOthers is true (the default) then a requested name that is not in the list - /// will be added to a static internal storage table, and its memory then reused for later - /// objects with the same name. If allowOthers is false, then the name request is rejected, and - /// the name is treated as null. - /// - /// \param name The new name - ASCII letters and underscores only, with optional integer suffix - /// \param allowedTypeNames A vector of allowed names. Storage locations must persist for the - /// entire run of the program. - /// \param allowOthers Whether a name not in allowedTypeNames is permitted. If true (the - /// default) then a name not in allowedTypeNames is added to a static internal storage vector - /// so that it can be reused later without additional memory allocation. + /** + * @brief Construct an indexed name from a name and a list of preset type + * names. + * + * Create an indexed name that is restricted to a list of preset type + * names. If it appears in that list, only a pointer to the character + * storage in the list is retained: the memory locations pointed at by the + * list must never be destroyed once they have been used to create + * names. If @p allowOthers is true (the default) then a requested name + * that is not in the list will be added to a static internal storage + * table, and its memory then reused for later objects with the same + * name. If @p allowOthers is false, then the name request is rejected, and + * the name is treated as null. + * + * @param[in] name The new name - ASCII letters and underscores only, with + * optional integer suffix. + * + * @param allowedTypeNames A vector of allowed names. Storage locations + * must persist for the entire run of the program. + * + * @param allowOthers Whether a name not in allowedTypeNames is + * permitted. If true (the default) then a name not in allowedTypeNames is + * added to a static internal storage vector so that it can be reused later + * without additional memory allocation. + */ IndexedName(const char* name, const std::vector& allowedTypeNames, bool allowOthers = true) @@ -102,11 +127,15 @@ public: set(name, -1, allowedTypeNames, allowOthers); } - /// Construct from a QByteArray, but explicitly making a copy of the name on its first - /// occurrence. If this is a name that has already been stored internally, no additional copy - /// is made. - /// - /// \param data The QByteArray to copy the data from + /** + * @brief Construct a mapped name from a QByteArray. + * + * Construct from a QByteArray, but explicitly making a copy of the name on + * its first occurrence. If this is a name that has already been stored + * internally, no additional copy is made. + * + * @param[in] data The QByteArray to copy the data from. + */ explicit IndexedName(const QByteArray& data) : type("") , index(0) @@ -114,13 +143,21 @@ public: set(data.constData(), data.size()); } - /// Given constant name and an index, reuse the existing memory for the name, not making a copy - /// of it, or scanning any existing storage for it. The name must never become invalid for the - /// lifetime of the object it names. This memory will never be reused by another object. - /// - /// \param name The name of the object. This memory is NOT copied and must be persistent. - /// \param index A positive, non-zero integer - /// \return An IndexedName with the given name and index, reusing the existing memory for name + /** + * @brief Create an indexed name from a string and index. + * + * Given constant name and an index, reuse the existing memory for the + * name, not making a copy of it, or scanning any existing storage for + * it. The name must never become invalid for the lifetime of the object it + * names. This memory will never be reused by another object. + * + * @param[in] name The name of the object. This memory is NOT copied and must be persistent. + * + * @param[in] index A positive, non-zero integer. + * + * @return An IndexedName with the given name and index, reusing the + * existing memory for name. + */ static IndexedName fromConst(const char* name, int index) { assert(index >= 0); @@ -130,11 +167,17 @@ public: return res; } - /// Given an existing std::string, *append* this name to it. If index is not zero, this will - /// include the index. - /// - /// \param buffer A (possibly non-empty) string buffer to append the name to. - /// \return A const char pointer to the name we appended to the buffer. + /** + * @brief Append this index name to a buffer. + * + * Given an existing std::string, *append* this name to it. If the index is + * not zero, this will include the index. + * + * @param[in,out] buffer A (possibly non-empty) string buffer to append the + * name to. + * + * @return A const char pointer to the name we appended to the buffer. + */ const char* appendToStringBuffer(std::string& buffer) const { // Note! buffer is not cleared on purpose. @@ -146,9 +189,11 @@ public: return buffer.c_str() + offset; } - /// Create and return a new std::string with this name in it. - /// - /// \return A newly-created string with the IndexedName in it (e.g. "EDGE42") + /** + * @brief Create and return a new std::string with this name in it. + * + * @return A newly-created string with the IndexedName in it (e.g. "EDGE42") + */ std::string toString() const { std::string result; @@ -156,8 +201,12 @@ public: return result; } - /// An indexedName is represented as the simple concatenation of the name and its index, e.g. - /// "EDGE1" or "FACE42". + /** + * @brief Append this indexed name to an output stream. + * + * An indexedName is represented as the simple concatenation of the name and its index, e.g. + * "EDGE1" or "FACE42". + */ friend std::ostream& operator<<(std::ostream& stream, const IndexedName& indexedName) { stream << indexedName.type; @@ -167,14 +216,26 @@ public: return stream; } - /// True only if both the name and index compare exactly equal. + /** + * Check if two indexed names are equal. + * + * @param[in] other The other IndexedName to compare against. + * + * @return True only if both the name and index compare exactly equal. + */ bool operator==(const IndexedName& other) const { return this->index == other.index && (this->type == other.type || std::strcmp(this->type, other.type) == 0); } - /// Increments the index by the given offset. Does not affect the text part of the name. + /** + * @brief Increments the index by the given offset. + * + * Does not affect the text part of the name. + * + * @param[in] offset The amount to increase the index by. + */ IndexedName& operator+=(int offset) { this->index += offset; @@ -189,8 +250,12 @@ public: return *this; } - /// Pre-decrement operator: decreases the index of this element by one. Must not make the index - /// negative (only checked when compiled in debug mode). + /** + * @brief Pre-decrement operator: decreases the index of this element by one. + * + * Must not make the index negative (only checked when compiled in debug + * mode). + */ IndexedName& operator--() { --this->index; @@ -198,13 +263,28 @@ public: return *this; } - /// True if either the name or the index compare not equal. + /** + * @brief Check if two indexed names are not equal. + * + * @param[in] other The other IndexedName to compare against. + * + * @return True if either the name or the index compare not equal. + */ bool operator!=(const IndexedName& other) const { return !(this->operator==(other)); } - /// Equivalent to C++20's operator <=> + /** + * @brief Compare two IndexedNames. + * + * Equivalent to C++20's operator <=>. The comparison is first + * lexicographical for the text and then numerical for the index. + * + * @param[in] other The other IndexedName to compare against. + * + * @return Negative value if this < other, positive if this > other, zero if equal. + */ int compare(const IndexedName& other) const { int res = std::strcmp(this->type, other.type); @@ -220,15 +300,19 @@ public: return 0; } - /// Provided to enable sorting operations: the comparison is first lexicographical for the text - /// element of the names, then numerical for the indices. + /// Check if this IndexedName is less than another. bool operator<(const IndexedName& other) const { return compare(other) < 0; } - /// Allow direct memory access to the individual characters of the text portion of the name. - /// NOTE: input is not range-checked when compiled in release mode. + /** + * @brief index into the text part of the name. + * + * Allow direct memory access to the individual characters of the text portion of the name. + * + * @note The input is not range-checked when compiled in release mode. + */ char operator[](int input) const { assert(input >= 0); @@ -238,7 +322,11 @@ public: return this->type[input]; } - /// Get a pointer to text part of the name - does NOT make a copy, returns direct memory access + /** + * @brief Get a pointer to text part of the name. + * + * Does NOT make a copy, returns direct memory access. + */ const char* getType() const { return this->type; @@ -250,17 +338,25 @@ public: return this->index; } - /// Set the numerical part of the name (note that there is no equivalent function to allow - /// changing the text part of the name, which is immutable once created). - /// - /// \param input The new index. Must be a positive non-zero integer + /** + * @brief Set the numerical part of the name + * + * @note There is no equivalent function to allow changing the text part of + * the name, which is immutable once created). + * + * @param[in] input The new index. Must be a positive non-zero integer. + */ void setIndex(int input) { assert(input >= 0); this->index = input; } - /// A name is considered "null" if its text component is an empty string. + /** + * @brief Check whether this index name is null. + * + * A name is considered "null" if its text component is an empty string. + */ // When we support C++20 we can use std::span<> to eliminate the clang-tidy warning // NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic bool isNull() const @@ -268,26 +364,37 @@ public: return this->type[0] == '\0'; } - /// Boolean conversion provides the opposite of isNull(), yielding true when the text part of - /// the name is NOT the empty string. + /** + * @brief Boolean conversion of the indexed name. + * + * Boolean conversion provides the opposite of isNull(), yielding true when + * the text part of the name is NOT the empty string. + */ explicit operator bool() const { return !isNull(); } protected: - /// Apply the IndexedName rules and either store the characters of a new type or a reference to - /// the characters in a type named in types, or stored statically within this function. If len - /// is not set, or set to -1 (the default), then the provided string in name is scanned for its - /// length using strlen (e.g. it must be null-terminated). - /// - /// \param name The new name. If necessary a copy is made, this char * need not be persistent - /// \param length The length of name - /// \param allowedNames A vector of storage locations of allowed names. These storage locations - /// must be persistent for the duration of the program run. - /// \param allowOthers If true (the default), then if name is not in allowedNames it is allowed, - /// and it is added to internal storage (making a copy of the name if this is its first - /// occurrence). + /** + * @brief Set the text part of the indexed name. + * + * Apply the IndexedName rules and either store the characters of a new + * type or a reference to the characters in a type named in types, or + * stored statically within this function. If len is not set, or set to -1 + * (the default), then the provided string in name is scanned for its + * length using strlen (e.g. it must be null-terminated). + * + * @param[in] name The new name. If necessary, a copy is made. The + * provided string need not be persistent. + * @param[in] length The length of name. + * @param allowedNames A vector of storage locations of allowed + * names. These storage locations must be persistent for the duration of + * the program run. + * @param[in] allowOthers If true (the default), then if name is not in + * allowedNames, it is allowed and it is added to internal storage (making + * a copy of the name if this is its first occurrence). + */ void set(const char* name, int length = -1, const std::vector& allowedNames = {}, diff --git a/src/App/MappedElement.h b/src/App/MappedElement.h index 4880354592..72d8943c61 100644 --- a/src/App/MappedElement.h +++ b/src/App/MappedElement.h @@ -35,21 +35,36 @@ class DocumentObject; namespace Data { -/// A MappedElement combines a MappedName and and IndexedName into a single entity and provides -/// simple comparison operators for the combination (including operator< so that the entity can -/// be sorted, or used in sorted containers). +/** + * @brief A combination of a MappedName and an IndexedName. + * @ingroup ElementMapping + * + * A mapped element combines a mapped name and an indexed name into a single + * entity and provides simple comparison operators for the combination + * (including operator< so that the entity can be sorted, or used in sorted + * containers). + */ struct AppExport MappedElement { + /// The indexed name. IndexedName index; + /// The mapped name. MappedName name; MappedElement() = default; + /** + * @brief Construct a mapped element from an indexed name and a mapped name. + * + * @param[in] idx The indexed name. + * @param[in] n The mapped name. + */ MappedElement(const IndexedName& idx, MappedName n) : index(idx) , name(std::move(n)) {} + ///@copydoc MappedElement(const IndexedName& idx, MappedName n) MappedElement(MappedName n, const IndexedName& idx) : index(idx) , name(std::move(n)) @@ -83,9 +98,14 @@ struct AppExport MappedElement return this->index != other.index || this->name != other.name; } - /// For sorting purposes, one MappedElement is considered "less" than another if its index - /// compares less (which is first alphabetical, and then by numeric index). If the index of this - /// MappedElement is the same, then the names are compared lexicographically. + /** + * @brief Compare two mapped elements. + * + * For sorting purposes, one MappedElement is considered "less" than + * another if its index compares less (which is first alphabetical, and + * then by numeric index). If the index of this MappedElement is the same, + * then the names are compared lexicographically. + */ bool operator<(const MappedElement& other) const { int res = this->index.compare(other.index); @@ -99,6 +119,7 @@ struct AppExport MappedElement } }; +/// Struct to represent an item in the history of an object. struct AppExport HistoryItem { App::DocumentObject* obj; @@ -109,19 +130,20 @@ struct AppExport HistoryItem HistoryItem(App::DocumentObject* obj, const Data::MappedName& name); }; +///Comparator struct to make element name sorting more stable. struct AppExport ElementNameComparator { - /** Comparison function to make topo name more stable + /** + * @brief Comparison function to make topo name more stable. * - * The sorting decomposes the name into either of the following two forms - * '#' + hex_digits + tail - * non_digits + digits + tail + * The sorting decomposes the name into either of the following two forms: + * - '#' + hex_digits + tail + * - non_digits + digits + tail * - * The non-digits part is compared lexically, while the digits part is - * compared by its integer value. - * - * The reason for this is to prevent names with bigger digits (which usually means - * they come later in history) from coming earlier when sorting. + * The non-digits part is compared lexicographically, while the digits part + * is compared by its integer value. The reason for this is to prevent + * names with bigger digits (which usually means they come later in + * history) from coming earlier when sorting. */ bool operator()(const MappedName& leftName, const MappedName& rightName) const; }; diff --git a/src/App/MappedName.h b/src/App/MappedName.h index b7df053cc4..29ec0518af 100644 --- a/src/App/MappedName.h +++ b/src/App/MappedName.h @@ -45,20 +45,30 @@ namespace Data // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) -/// The MappedName class maintains a two-part name: the first part ("data") is considered immutable -/// once created, while the second part ("postfix") can be modified/appended to by later operations. -/// It uses shared data when possible (see the fromRawData() members). Despite storing data and -/// postfix separately, they can be accessed via calls to size(), operator[], etc. as though they -/// were a single array. +/** + * @brief A class for managing element map names. + * @ingroup ElementMapping + * + * The MappedName class maintains a two-part name: the first part ("data") is + * considered immutable once created, while the second part ("postfix") can be + * modified/appended to by later operations. It uses shared data when possible + * (see the fromRawData() members). Despite storing data and postfix + * separately, they can be accessed via calls to size(), operator[], etc. as + * though they were a single array. + */ class AppExport MappedName { public: - /// Create a MappedName from a C string, optionally prefixed by an element map prefix, which - /// will be omitted from the stored MappedName. - /// - /// \param name The new name. A deep copy is made. - /// \param size Optional, the length of the name string. If not provided, the string must be - /// null-terminated. + /** + * @brief Create a MappedName from a C string. + * + * Create a MappedName from a C string, optionally prefixed by an element + * map prefix, which will be omitted from the stored MappedName. + * + * @param[in] name The new name. A deep copy is made. + * @param[in] size Optional, the length of the name string. If not + * provided, the string must be null-terminated. + */ explicit MappedName(const char* name, int size = -1) : raw(false) { @@ -72,10 +82,14 @@ public: data = size < 0 ? QByteArray(name) : QByteArray(name, size); } - /// Create a MappedName from a C++ std::string, optionally prefixed by an element map prefix, - /// which will be omitted from the stored MappedName. - /// - /// \param name The new name. A deep copy is made. + /** + * @brief Create a MappedName from a C++ std::string. + * + * Create a MappedName from a C++ std::string, optionally prefixed by an + * element map prefix, which will be omitted from the stored MappedName. + * + * @param nameString The new name. A deep copy is made. + */ explicit MappedName(const std::string& nameString) : raw(false) { @@ -88,9 +102,14 @@ public: data = QByteArray(name, static_cast(size)); } - /// Create a MappedName from an IndexedName. If non-zero, the numerical part of the IndexedName - /// is appended as text to the MappedName. In that case the memory is *not* shared between the - /// original IndexedName and the MappedName. + /** + * @brief Create a MappedName from an IndexedName. + * + * Create a MappedName from an IndexedName. If non-zero, the numerical part + * of the IndexedName is appended as text to the MappedName. In that case + * the memory is *not* shared between the original IndexedName and the + * MappedName. + */ explicit MappedName(const IndexedName& element) : data(QByteArray::fromRawData(element.getType(), static_cast(qstrlen(element.getType())))) @@ -102,6 +121,8 @@ public: } } + + /// Create a MappedName from a StringIdRef. explicit MappedName(const App::StringIDRef& sid) : raw(false) { @@ -114,29 +135,39 @@ public: MappedName(const MappedName& other) = default; - /// Copy constructor with start position offset and optional size. The data is *not* reused. - /// - /// \param other The MappedName to copy - /// \param startPosition an integer offset to start the copy from - /// \param size the number of bytes to copy. - /// \see append() for details about how the copy behaves for various sizes and start positions + /** + * @brief Copy constructor with start position offset and size. + * + * Copy constructor with start position offset and optional size. The data + * is *not* reused. + * + * @param[in] other The MappedName to copy. + * @param[in] startPosition An integer offset to start the copy from. + * @param[in] size The number of bytes to copy. + * + * @see append() for details about how the copy behaves for various sizes + * and start positions + */ MappedName(const MappedName& other, int startPosition, int size = -1) : raw(false) { append(other, startPosition, size); } - /// Copy constructor with additional postfix - /// - /// \param other The mapped name to copy. Its data and postfix become the new MappedName's data - /// \param postfix The postfix for the new MappedName + /** + * @brief Copy constructor with additional postfix + * + * @param[in] other The mapped name to copy. Its data and postfix become + * the new MappedName's data. + * + * @param postfix The postfix for the new MappedName. + */ MappedName(const MappedName& other, const char* postfix) : data(other.data + other.postfix) , postfix(postfix) , raw(false) {} - /// Move constructor MappedName(MappedName&& other) noexcept : data(std::move(other.data)) , postfix(std::move(other.postfix)) @@ -145,12 +176,17 @@ public: ~MappedName() = default; - /// Construct a MappedName from raw character data (including null characters, if size is - /// provided). No copy is made: the data is used in place. - /// - /// \param name The raw data to use. - /// \param size The number of bytes to access. If omitted, name must be null-terminated. - /// \return a new MappedName with name as its data. + /** + * @brief Construct a MappedName from raw character data. + * + * Construct a MappedName from raw character data (including null characters, if size is + * provided). No copy is made: the data is used in place. + * + * @param name The raw data to use. + * @param size The number of bytes to access. If omitted, name must be null-terminated. + * + * @return a new MappedName with name as its data. + */ static MappedName fromRawData(const char* name, int size = -1) { MappedName res; @@ -162,25 +198,36 @@ public: return res; } - /// Construct a MappedName from QByteArray data (including any embedded null characters). - /// - /// \param data The original data. No copy is made, the data is shared with the other instance. - /// \return a new MappedName with data as its data. + /** + * @brief Construct a MappedName from QByteArray data. + * + * Construct a MappedName from QByteArray data (including any embedded null characters). + * + * @param[in] data The original data. No copy is made, the data is shared + * with the other instance. + * + * @return a new MappedName with data as its data. + */ static MappedName fromRawData(const QByteArray& data) { return fromRawData(data.constData(), data.size()); } - /// Construct a MappedName from another MappedName - /// - /// \param other The MappedName to copy from. The data is usually not copied, but in some - /// cases a partial copy may be made to support a slice that extends across other's data into - /// its postfix. - /// \param startPosition The position to start the reference at. - /// \param size The number of bytes to access. If omitted, continues from startPosition - /// to the end of available data (including postfix). - /// \return a new MappedName sharing (possibly a subset of) data with other. - /// \see append() for details about how the copy behaves for various sizes and start positions + /** + * @brief Construct a MappedName from another MappedName. + * + * @param[in] other The MappedName to copy from. The data is usually not + * copied, but in some cases a partial copy may be made to support a slice + * that extends across other's data into its postfix. + * @param[in] startPosition The position to start the reference at. + * @param[in] size The number of bytes to access. If omitted, continues + * from startPosition to the end of available data (including postfix). + * + * @return a new MappedName sharing (possibly a subset of) data with other. + * + * @see append() For details about how the copy behaves for various sizes + * and start positions. + */ static MappedName fromRawData(const MappedName& other, int startPosition, int size = -1) { if (startPosition < 0) { @@ -218,7 +265,7 @@ public: return res; } - /// Share data with another MappedName + /// Share data with another MappedName. MappedName& operator=(const MappedName& other) = default; /// Create a new MappedName from a std::string: the string's data is copied. @@ -235,8 +282,7 @@ public: return *this; } - - /// Move-construct a MappedName + /// Move-construct a MappedName. MappedName& operator=(MappedName&& other) noexcept { this->data = std::move(other.data); @@ -245,8 +291,12 @@ public: return *this; } - /// Write to a stream as the name with postfix directly appended to it. Note that there is no - /// special handling for null or non-ASCII characters, they are simply written to the stream. + /** + * @brief Write to a stream as the name with postfix directly appended to it. + * + * Note that there is no special handling for null or non-ASCII characters, + * they are simply written to the stream. + */ friend std::ostream& operator<<(std::ostream& stream, const MappedName& mappedName) { stream.write(mappedName.data.constData(), mappedName.data.size()); @@ -254,8 +304,11 @@ public: return stream; } - /// Two MappedNames are equal if the concatenation of their data and postfix is equal. The - /// individual data and postfix may NOT be equal in this case. + /** + * @brief Two MappedNames are equal if the concatenation of their data and postfix is equal. + * + * The individual data and postfix may NOT be equal in this case. + */ bool operator==(const MappedName& other) const { if (this->size() != other.size()) { @@ -286,13 +339,21 @@ public: return tmp == larger.postfix; } + /// Check if two mapped names are inequal. bool operator!=(const MappedName& other) const { return !(this->operator==(other)); } - /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS - /// argument's postfix with the RHS argument's data and postfix appended to it. + /** + * @brief Concatenate two mapped names. + * + * @param[in] other The mapped name to append. + * + * @return A new MappedName whose data is the LHS argument's data and whose + * postfix is the LHS argument's postfix with the RHS argument's data and + * postfix appended to it. + */ MappedName operator+(const MappedName& other) const { MappedName res(*this); @@ -300,8 +361,15 @@ public: return res; } - /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS - /// argument's postfix with the RHS argument appended to it. The character data is copied. + /** + * @brief Concatenate two mapped names. + * + * @param[in] other The mapped name as a string to append. + * + * @return A new MappedName whose data is the LHS argument's data and whose + * postfix is the LHS argument's postfix with the RHS argument appended to + * it. The character data is copied. + */ MappedName operator+(const char* other) const { MappedName res(*this); @@ -309,8 +377,7 @@ public: return res; } - /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS - /// argument's postfix with the RHS argument appended to it. The character data is copied. + /// @copydoc operator+(const char*) const MappedName operator+(const std::string& other) const { MappedName res(*this); @@ -318,8 +385,15 @@ public: return res; } - /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS - /// argument's postfix with the RHS argument appended to it. + /** + * @brief Concatenate two mapped names. + * + * @param[in] other The mapped name as a QByteArray. + * + * @return A new MappedName whose data is the LHS argument's data and whose + * postfix is the LHS argument's postfix with the RHS argument appended to + * it. + */ MappedName operator+(const QByteArray& other) const { MappedName res(*this); @@ -327,8 +401,16 @@ public: return res; } - /// Appends other to this instance's postfix. other must be a null-terminated C string. The - /// character data from the string is copied. + /** + * @brief Appends other to this instance's postfix. + * + * @p other must be a null-terminated C string. The character data from the + * string is copied. + * + * @param[in] other The mapped name as a string. + * + * @return This with the other's postfix appended. + */ MappedName& operator+=(const char* other) { if (other && (other[0] != 0)) { @@ -337,7 +419,7 @@ public: return *this; } - /// Appends other to this instance's postfix. The character data from the string is copied. + /// @copydoc operator+=(const char* other) MappedName& operator+=(const std::string& other) { if (!other.empty()) { @@ -347,29 +429,50 @@ public: return *this; } - /// Appends other to this instance's postfix. The data may be either copied or shared, depending - /// on whether this->postfix is empty (in which case the data is shared) or non-empty (in which - /// case it is copied). + /** + * @brief Appends other to this instance's postfix. + * + * The data may be either copied or shared, depending on whether + * this->postfix is empty (in which case the data is shared) or non-empty + * (in which case it is copied). + * + * @param[in] other The mapped name as a QByteArray. + * @return This with the other's data appended. + */ MappedName& operator+=(const QByteArray& other) { this->postfix += other; return *this; } - /// Appends other to this instance's postfix, unless this is empty, in which case this acts - /// like operator=, and makes this instance's data equal to other's data, and this instance's - /// postfix equal to the other instance's postfix. + /** + * @brief Appends other to this instance's postfix. + * + * Appends other to this instance's postfix, unless this is empty, in which + * case this acts like operator=, and makes this instance's data equal to + * other's data, and this instance's postfix equal to the other instance's + * postfix. + * + * @param[in] other The mapped name to append. + * @return This with the other's data appended. + */ MappedName& operator+=(const MappedName& other) { append(other); return *this; } - /// Add dataToAppend to this MappedName. If the current name is empty, this becomes the new - /// data element. If this MappedName already has data, then the data is appended to the postfix. - /// - /// \param dataToAppend The data to add. A deep copy is made. - /// \param size The number of bytes to copy. If omitted, dataToAppend must be null-terminated. + /** + * @brief Append to this mapped name. + * + * Add @p dataToAppend to this mapped name. If the current name is empty, + * this becomes the new data element. If this MappedName already has data, + * then the data is appended to the postfix. + * + * @param[in] dataToAppend The data to add. A deep copy is made. + * @param[in] size The number of bytes to copy. If omitted, @p dataToAppend + * must be null-terminated. + */ void append(const char* dataToAppend, int size = -1) { if (dataToAppend && (size != 0)) { @@ -385,20 +488,30 @@ public: } } - /// Treating both this and other as single continuous byte arrays, append other to this. If this - /// is empty, then other's data is shared with this instance's data beginning at startPosition. - /// If this is *not* empty, then all data is appended to the postfix. If the copy crosses the - /// boundary between other's data and its postfix, then if this instance was empty, the new - /// data stops where other's data stops, and the remainder of the copy is placed in the suffix. - /// Otherwise the copy simply continues as though there was no distinction between other's - /// data and suffix. - /// - /// \param other The MappedName to obtain the data from. The data is shared when possible, - /// depending on the details of startPosition, size, and this->empty(). - /// \param startPosition The byte to start the copy at. Must be a positive non-zero integer less - /// than the length of other's combined data + postfix. - /// \param size The number of bytes to copy. Must not overrun the end of other's combined data - /// storage when taking startPosition into consideration. + /** + * @brief Append to this mapped name. + * + * Treating both this and other as single continuous byte arrays, append + * other to this. If this is empty, then other's data is shared with this + * instance's data beginning at @p startPosition. If this is *not* empty, + * then all data is appended to the postfix. If the copy crosses the + * boundary between other's data and its postfix, then if this instance was + * empty, the new data stops where other's data stops, and the remainder of + * the copy is placed in the suffix. Otherwise the copy simply continues + * as though there was no distinction between other's data and suffix. + * + * @param[in] other The MappedName to obtain the data from. The data is + * shared when possible, depending on the details of startPosition, size, + * and this->empty(). + * + * @param startPosition The byte to start the copy at. Must be a positive + * non-zero integer less than the length of other's combined data + + * postfix. + * + * @param size The number of bytes to copy. Must not overrun the end of + * other's combined data storage when taking @p startPosition into + * consideration. + */ void append(const MappedName& other, int startPosition = 0, int size = -1) { // enforce 0 <= startPosition <= other.size @@ -460,31 +573,49 @@ public: } } - /// Create a std::string from this instance, starting at startPosition, and extending len bytes. - /// - /// \param startPosition The offset into the data - /// \param len The number of bytes to output - /// \return A new std::string containing the bytes copied from this instance's data and postfix - /// (depending on startPosition and len). - /// \note No effort is made to ensure that these are valid ASCII characters, and it is possible - /// the data includes embedded null characters, non-ASCII data, etc. + /** + * @brief Create a string representation. + * + * Create a std::string from this instance, starting at startPosition, and + * extending len bytes. + * + * @param[in] startPosition The offset into the data. + * @param[in] len The number of bytes to output. + * + * @return A new std::string containing the bytes copied from this + * instance's data and postfix (depending on startPosition and len). + * + * @note No effort is made to ensure that these are valid ASCII characters, + * and it is possible the data includes embedded null characters, non-ASCII + * data, etc. + */ std::string toString(int startPosition = 0, int len = -1) const { std::string res; return appendToBuffer(res, startPosition, len); } - /// Given a (possibly non-empty) std::string buffer, append this instance to it, starting at a - /// specified position, and continuing for a specified number of bytes. - /// - /// \param buffer The string buffer to append to. - /// \param startPosition The position in this instance's data/postfix to start at (defaults to - /// zero). Must be less than the total length of the data plus the postfix. - /// \param len The number of bytes to append. If omitted, defaults to appending all available - /// data starting at startPosition. - /// \return A pointer to the beginning of the appended data within buffer. - /// \note No effort is made to ensure that these are valid ASCII characters, and it is possible - /// the data includes embedded null characters, non-ASCII data, etc. + /** + * @brief Append this mapped name to a buffer. + * + * Given a (possibly non-empty) std::string buffer, append this instance to it, starting at a + * specified position, and continuing for a specified number of bytes. + * + * @param[in,out] buffer The string buffer to append to. + * + * @param[in] startPosition The position in this instance's data/postfix to + * start at (defaults to * zero). Must be less than the total length of the + * data plus the postfix. + * + * @param[in] len The number of bytes to append. If omitted, defaults to + * appending all available data starting at startPosition. + * + * @return A pointer to the beginning of the appended data within buffer. + * + * @note No effort is made to ensure that these are valid ASCII characters, + * and it is possible the data includes embedded null characters, non-ASCII + * data, etc. + */ const char* appendToBuffer(std::string& buffer, int startPosition = 0, int len = -1) const { std::size_t offset = buffer.size(); @@ -531,16 +662,24 @@ public: return this->data.constData() + offset; } - /// Get access to raw byte data. When possible, data is shared between this instance and the - /// returned QByteArray. If the combination of offset and size results in data that crosses the - /// boundary between this->data and this->postfix, the data must be copied in order to provide - /// access as a continuous array of bytes. - /// - /// \param offset The start position of the raw data access. - /// \param size The number of bytes to access. If omitted, the resulting QByteArray includes - /// everything starting from offset to the end, including any postfix data. - /// \return A new QByteArray that shares data with this instance if possible, or is a new copy - /// if required by offset and size. + /** + * @brief Convert this mapped name to raw bytes. + * + * Get access to raw byte data. When possible, data is shared between this + * instance and the returned QByteArray. If the combination of offset and + * size results in data that crosses the boundary between this->data and + * this->postfix, the data must be copied in order to provide access as a + * continuous array of bytes. + * + * @param[in] offset The start position of the raw data access. + * + * @param[in] size The number of bytes to access. If omitted, the resulting + * QByteArray includes everything starting from offset to the end, + * including any postfix data. + * + * @return A new QByteArray that shares data with this instance if + * possible, or is a new copy if required by offset and size. + */ QByteArray toRawBytes(int offset = 0, int size = -1) const { if (offset < 0) { @@ -585,10 +724,16 @@ public: // No constData() because 'data' is allowed to contain raw data, which may not end with 0. - /// Provide access to the content of this instance. If either postfix or data is empty, no copy - /// is made and the original QByteArray is returned, sharing data with this instance. If this - /// instance contains both data and postfix, a new QByteArray is created and stores a copy of - /// the data and postfix concatenated together. + /** + * @brief Provide access to the content of this instance. + * + * If either postfix or data is empty, no copy is made and the original + * QByteArray is returned, sharing data with this instance. If this + * instance contains both data and postfix, a new QByteArray is created and + * stores a copy of the data and postfix concatenated together. + * + * @return Either a new or the current QByteArray. + */ QByteArray toBytes() const { if (this->postfix.isEmpty()) { @@ -600,13 +745,19 @@ public: return this->data + this->postfix; } - /// Create an IndexedName from the data portion of this MappedName. If this data has a postfix, - /// the function returns an empty IndexedName. The function will fail if this->data contains - /// anything other than the ASCII letter a-z, A-Z, and the underscore, with an optional integer - /// suffix, returning an empty IndexedName (e.g. an IndexedName that evaluates to boolean - /// false and isNull() == true). - /// - /// \return a new IndexedName that shares its data with this instance's data member. + /** + * @brief Create an IndexedName from the mapped name. + * + * Create an IndexedName from the data portion of this MappedName. If this + * data has a postfix, the function returns an empty IndexedName. The + * function will fail if this->data contains anything other than the ASCII + * letter a-z, A-Z, and the underscore, with an optional integer suffix, + * returning an empty IndexedName (e.g. an IndexedName that evaluates to + * boolean false and isNull() == true). + * + * @return a new IndexedName that shares its data with this instance's data + * member. + */ IndexedName toIndexedName() const { if (this->postfix.isEmpty()) { @@ -615,8 +766,13 @@ public: return IndexedName(); } - /// Create and return a string version of this MappedName prefixed by the ComplexGeoData element - /// map prefix, if this MappedName cannot be converted to an indexed name. + /** + * @brief Create a prefixed string from the mapped name. + * + * Create and return a string version of this MappedName prefixed by the + * ComplexGeoData element map prefix, if this MappedName cannot be + * converted to an indexed name. + */ std::string toPrefixedString() const { std::string res; @@ -624,11 +780,18 @@ public: return res; } - /// Append this MappedName to a provided string buffer, including the ComplexGeoData element - /// map prefix if the MappedName cannot be converted to an IndexedName. - /// - /// \param buf A (possibly non-empty) string to append this MappedName to. - /// \return A pointer to the beginning of the buffer. + /** + * @brief Append this mapped name to a string buffer. + * + * Append this MappedName to a provided string buffer, including the + * ComplexGeoData element map prefix if the MappedName cannot be converted + * to an IndexedName. + * + * @param[in,out] buf A (possibly non-empty) string to append this + * MappedName to. + * + * @return A pointer to the beginning of the buffer. + */ const char* appendToBufferWithPrefix(std::string& buf) const { if (!toIndexedName()) { @@ -638,10 +801,20 @@ public: return buf.c_str(); } - /// Equivalent to C++20 operator<=>. Performs byte-by-byte comparison of this and other, - /// starting at the first byte and continuing through both data and postfix, ignoring which is - /// which. If the combined data and postfix members are of unequal size but start with the same - /// data, the shorter array is considered "less than" the longer. + /** + * @brief Compare two mapped names. + * + * Equivalent to C++20 operator<=>. Performs byte-by-byte comparison of + * this and other, starting at the first byte and continuing through both + * data and postfix, ignoring which is which. If the combined data and + * postfix members are of unequal size but start with the same data, the + * shorter array is considered "less than" the longer. + * + * @param[in] other The mapped name to compare. + * + * @return < 0 if this is less than other, 0 if they are equal and > 0 if + * this is greater than other. + */ int compare(const MappedName& other) const { int thisSize = this->size(); @@ -665,14 +838,23 @@ public: return 0; } - /// \see compare() + /// Check if this mapped name is less than @p other. bool operator<(const MappedName& other) const { return compare(other) < 0; } - /// Treat this MappedName as a single continuous array of bytes, beginning with data and - /// continuing through postfix. No bounds checking is performed when compiled in release mode. + /** + * @brief Index into the mapped name. + * + * Treat this MappedName as a single continuous array of bytes, beginning + * with data and continuing through postfix. + * + * @param index The byte offset to access. + * + * @return The byte at the specified offset, or 0 if the offset is out of + * range. + */ char operator[](int index) const { if (index < 0) { @@ -687,31 +869,52 @@ public: return this->data[index]; } - /// Treat this MappedName as a single continuous array of bytes, returning the combined size - /// of the data and postfix. + /** + * @brief Get the combined size of data and postfix. + * + * Treat this MappedName as a single continuous array of bytes, returning the combined size + * of the data and postfix. + * + * @return The total number of bytes in data + postfix. + */ int size() const { return this->data.size() + this->postfix.size(); } - /// Treat this MappedName as a single continuous array of bytes, returning true only if both - /// data and prefix are empty. + /** + * @brief Check if the mapped name is empty. + * + * Treat this MappedName as a single continuous array of bytes, returning true only if both + * data and prefix are empty. + * + * @return true if there are no bytes in data or postfix. + */ bool empty() const { return this->data.isEmpty() && this->postfix.isEmpty(); } - /// Returns true if this is shared data, or false if a unique copy has been made. - /// It is safe to access data only if it has been copied prior. To force a copy - /// please \see compact() + /** + * @brief Check whether this mapped name is shared data. + * + * It is safe to access data only if it has been copied prior. To force a copy + * please \see compact(). + * + * @return True if this is shared data, or false if a unique copy has been made. + */ bool isRaw() const { return this->raw; } - /// If this is shared data, a new unshared copy is made and returned. If it is already unshared - /// no new copy is made, a new instance is returned that shares is data with the current - /// instance. + /** + * @brief Copy the mapped name. + * + * If this is shared data, a new unshared copy is made and returned. If it + * is already unshared no new copy is made, a new instance is returned that + * shares is data with the current instance. + */ MappedName copy() const { if (!this->raw) { @@ -726,14 +929,22 @@ public: /// Ensure that this data is unshared, making a copy if necessary. void compact() const; - /// Boolean conversion is the inverse of empty(), returning true if there is data in either the - /// data or postfix, and false if there is nothing in either. + /** + * @brief Boolean conversion is the inverse of empty(). + * + * @return True if there is data in either the data or postfix, and false + * if there is nothing in either. + */ explicit operator bool() const { return !empty(); } - /// Reset this instance, clearing anything in data and postfix. + /** + * @brief Reset this instance. + * + * Clear anything in data and postfix. + */ void clear() { this->data.clear(); @@ -741,12 +952,19 @@ public: this->raw = false; } - /// Find a string of characters in this MappedName. The bytes must occur either entirely in the - /// data, or entirely in the postfix: a string that overlaps the two will not be found. - /// - /// \param searchTarget A null-terminated C string to search for. - /// \param startPosition A byte offset to start the search at. - /// \return The position of the target in this instance, or -1 if the target is not found. + /** + * @brief Find a string of characters in this mapped name. + * + * Find a string of characters in this mapped name. The bytes must occur + * either entirely in the data, or entirely in the postfix: a string that + * overlaps the two will not be found. + * + * @param[in] searchTarget A null-terminated C string to search for. + * @param[in] startPosition A byte offset to start the search at. + * + * @return The position of the target in this instance, or -1 if the target + * is not found. + */ int find(const char* searchTarget, int startPosition = 0) const { if (!searchTarget) { @@ -772,25 +990,29 @@ public: return res + this->data.size(); } - /// Find a string of characters in this MappedName. The bytes must occur either entirely in the - /// data, or entirely in the postfix: a string that overlaps the two will not be found. - /// - /// \param searchTarget A string to search for. - /// \param startPosition A byte offset to start the search at. - /// \return The position of the target in this instance, or -1 if the target is not found. + /// @copydoc find(const char*,int) const int find(const std::string& searchTarget, int startPosition = 0) const { return find(searchTarget.c_str(), startPosition); } - /// Find a string of characters in this MappedName, starting at the back of postfix and - /// proceeding in reverse through the data. The bytes must occur either entirely in the - /// data, or entirely in the postfix: a string that overlaps the two will not be found. - /// - /// \param searchTarget A null-terminated C string to search for. - /// \param startPosition A byte offset to start the search at. Negative numbers are supported - /// and count back from the end of the concatenated data (as in QByteArray::lastIndexOf()). - /// \return The position of the target in this instance, or -1 if the target is not found. + /** + * @brief Reverse find a string of characters in this mapped name. + * + * Find a string of characters in this mapped name, starting at the back of + * postfix and proceeding in reverse through the data. The bytes must occur + * either entirely in the data, or entirely in the postfix: a string that + * overlaps the two will not be found. + * + * @param[in] searchTarget A null-terminated C string to search for. + * + * @param[in] startPosition A byte offset to start the search at. Negative + * numbers are supported and count back from the end of the concatenated + * data (as in QByteArray::lastIndexOf()). + * + * @return The position of the target in this instance, or -1 if the target + * is not found. + */ int rfind(const char* searchTarget, int startPosition = -1) const { if (!searchTarget) { @@ -809,22 +1031,23 @@ public: return this->data.lastIndexOf(searchTarget, startPosition); } - /// Find a string in this MappedName, starting at the back of postfix and proceeding in reverse - /// through the data. The bytes must occur either entirely in the data, or entirely in the - /// postfix: a string that overlaps the two will not be found. - /// - /// \param searchTarget A null-terminated C string to search for. - /// \param startPosition A byte offset to start the search at. Negative numbers are supported - /// and count back from the end of the concatenated data (as in QByteArray::lastIndexOf()). - /// \return The position of the target in this instance, or -1 if the target is not found. + /// @copydoc rfind(const char*,int) const int rfind(const std::string& searchTarget, int startPosition = -1) const { return rfind(searchTarget.c_str(), startPosition); } - /// Returns true if this MappedName ends with the search target. If there is a postfix, only the - /// postfix is considered. If not, then only the data is considered. A search string that - /// overlaps the two will not be found. + /** + * @brief Check if this mapped name ends with the search target. + * + * If there is a postfix, only the postfix is considered. If not, then only + * the data is considered. A search string that overlaps the two will not + * be found. + * + * @param[in] searchTarget The string to search for at the end of this mapped name. + * + * @return true if this MappedName ends with the search target. + */ bool endsWith(const char* searchTarget) const { if (!searchTarget) { @@ -836,21 +1059,24 @@ public: return this->data.endsWith(searchTarget); } - /// Returns true if this MappedName ends with the search target. If there is a postfix, only the - /// postfix is considered. If not, then only the data is considered. A search string that - /// overlaps the two will not be found. + /// @copydoc endsWith(const char*) const bool endsWith(const std::string& searchTarget) const { return endsWith(searchTarget.c_str()); } - /// Returns true if this MappedName starts with the search target. If there is a postfix, only - /// the postfix is considered. If not, then only the data is considered. A search string that - /// overlaps the two will not be found. - /// - /// \param searchTarget An array of bytes to match - /// \param offset An offset to perform the match at - /// \return True if this MappedName begins with the target bytes + /** + * @brief Check if this mapped name starts with the search target. + * + * If there is a postfix, only the postfix is considered. If not, then only + * the data is considered. A search string that overlaps the two will not + * be found. + * + * @param[in] searchTarget The search target to match. + * @param[in] offset An offset to perform the match at. + * + * @return True if this MappedName begins with the target bytes. + */ bool startsWith(const QByteArray& searchTarget, int offset = 0) const { if (searchTarget.size() > size() - offset) { @@ -866,13 +1092,7 @@ public: return this->postfix.startsWith(searchTarget); } - /// Returns true if this MappedName starts with the search target. If there is a postfix, only - /// the postfix is considered. If not, then only the data is considered. A search string that - /// overlaps the two will not be found. - /// - /// \param searchTarget An array of bytes to match - /// \param offset An offset to perform the match at - /// \return True if this MappedName begins with the target bytes + /// @copydoc startsWith(const QByteArray&,int) const bool startsWith(const char* searchTarget, int offset = 0) const { if (!searchTarget) { @@ -883,13 +1103,7 @@ public: offset); } - /// Returns true if this MappedName starts with the search target. If there is a postfix, only - /// the postfix is considered. If not, then only the data is considered. A search string that - /// overlaps the two will not be found. - /// - /// \param searchTarget A string to match - /// \param offset An offset to perform the match at - /// \return True if this MappedName begins with the target bytes + /// @copydoc startsWith(const QByteArray&,int) const bool startsWith(const std::string& searchTarget, int offset = 0) const { return startsWith( @@ -897,19 +1111,26 @@ public: offset); } - /// Extract tagOut and other information from a encoded element name - /// - /// \param tagOut: optional pointer to receive the extracted tagOut - /// \param lenOut: optional pointer to receive the length field after the tagOut field. - /// This gives the length of the previous hashed element name starting - /// from the beginning of the give element name. - /// \param postfixOut: optional pointer to receive the postfixOut starting at the found tagOut - /// field. \param typeOut: optional pointer to receive the element typeOut character \param - /// negative: return negative tagOut as it is. If disabled, then always return positive tagOut. - /// Negative tagOut is sometimes used for element disambiguation. - /// \param recursive: recursively find the last non-zero tagOut - /// - /// \return Return the end position of the tagOut field, or return -1 if not found. + /** + * @brief Extract information from an encoded element name. + * + * Extract tagOut and other information from an encoded element name. + * + * @param[out] tagOut: optional pointer to receive the extracted tagOut + * @param[out] lenOut: optional pointer to receive the length field after + * the tagOut field. This gives the length of the previous hashed element + * name starting from the beginning of the give element name. + * @param[out] postfixOut: optional pointer to receive the postfixOut + * starting at the found tagOut field. + * @param[out] typeOut: optional pointer to receive the element typeOut character + * @param[in] negative: return negative tagOut as it is. If disabled, then + * always return positive tagOut. Negative tagOut is sometimes used for + * element disambiguation. + * @param[in] recursive: recursively find the last non-zero tagOut + * + * @return Return the end position of the tagOut field, or return -1 if not + * found. + */ int findTagInElementName(long* tagOut = nullptr, int* lenOut = nullptr, std::string* postfixOut = nullptr, From 0537bd6c36f926a8c7d2364f216fec7ae31f8371 Mon Sep 17 00:00:00 2001 From: Pieter Hijma Date: Sun, 9 Nov 2025 16:42:16 +0100 Subject: [PATCH 7/7] Doc: Add a topic for element mapping --- src/App/core-app.dox | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/App/core-app.dox b/src/App/core-app.dox index 695b7658b3..55cf943198 100644 --- a/src/App/core-app.dox +++ b/src/App/core-app.dox @@ -3,6 +3,10 @@ * @ingroup CORE * @brief The part of FreeCAD that works without GUI (console or server mode) * + * @htmlonly + *

+ * @endhtmlonly + * * @details It contains the App namespace and defines core concepts such as * @ref ApplicationGroup "Application", @ref DocumentGroup "Document", @ref * DocumentObjectGroup "DocumentObject", @ref PropertyFramework "Property @@ -14,6 +18,10 @@ * in @ref App::Document "Document" and @ref App::DocumentObject * "DocumentObject". In addition, %App has a representation of the running * @ref App::Application "Application". + * + * @htmlonly + *
+ * @endhtmlonly */ /** @@ -827,6 +835,45 @@ * object are tracked and applied on the copy. */ +/** + * @defgroup ElementMapping Element Mapping for Topological naming. + * @ingroup APP + * @brief Element mapping system for topological naming + * + * Since the names of vertices, edges, and faces that we obtain from + * OpenCascade are not stable under modifications of the shape, FreeCAD + * implements a system called "topological naming" to provide stable names for + * these elements. + * + * A shape in FreeCAD is represented by Part::TopoShape, a subclass of + * Data::ComplexGeoData that contains much of the logic for element mapping. + * This logic mainly resides in package Data. + * + * A @ref Data::ComplexGeoData "ComplexGeoData" object contains a @ref + * Data::ComplexGeoData::Tag "Tag" that uniquely identifies the shape in the + * document. A @ref Data::ComplexGeoData "ComplexGeoData" object also contains + * a @ref Data::ElementMap "ElementMap" that maintains a mapping from an @ref + * Data::IndexedName "IndexedName" to a @ref Data::MappedName "MappedName". + * + * An indexed name, is a name that we obtain from the shapes from OpenCascade. + * A mapped name is a name that is formed by means of topological relations. A + * mapped name consists of an immutable base name (called the "data") while the + * second part is appended on operations on the shape. This is called the + * "postfix" of the mapped name. For a selection of used postfix tags, see + * the @ref ElementNameConstants "Element name constants" section in Data. + * + * An example of a mapped name is + * `Pocket.;g2;SKT;:H7cf,E;:G;XTR;:H7cf:7,F;:M;CUT;:H-7d0:7,F.Face8` which + * roughly means that the selected face `Face8` was modified `M` with a `CUT` + * operation. The `T` or `H` tags provide the tag of the shape in decimal and + * hexadecimal respectively. + * + * Because multiple operations can result in long mapped names, FreeCAD makes + * use of a string hasher that creates short hashes out of the long mapped + * names. An example of the hashed name for the above mapped name is: + * `Pocket.;#20:2;:M;CUT;:H-e61:7,F.Face8`. + */ + /** * @namespace App * @ingroup APP @@ -841,3 +888,10 @@ * For a more high-level discussion see the topic @ref APP "App". */ +/** + * @namespace Data + * @ingroup ElementMapping + * @brief The namespace for element names + * + */ +