Merge pull request #25199 from pieterhijma/doc-element-mapping

Doc: Improve the documentation of element mapping
This commit is contained in:
Chris Hennes
2026-02-03 21:06:53 +01:00
committed by GitHub
8 changed files with 1298 additions and 547 deletions

View File

@@ -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<SearchOption> 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<Base::Vector3d> points;
std::vector<Facet> 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<const char*> 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<std::string, unsigned long> 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<Base::Vector3d>& Points,
std::vector<Line>& 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<Base::Vector3d>& Points,
std::vector<Base::Vector3d>& PointNormals,
std::vector<Facet>& 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<Base::Vector3d>& Points,
std::vector<Base::Vector3d>& 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<Base::Vector3d>& Points,
std::vector<Line>& 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<Base::Vector3d>& Points,
std::vector<Facet>& 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<Base::Vector3d> centerOfGravity() const;
//@}
/**
* @brief Get the center of gravity.
*
* @return The center of gravity if available.
*/
virtual std::optional<Base::Vector3d> 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<std::pair<MappedName, ElementIDRefs>>
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<MappedName>* history = nullptr) const
@@ -323,49 +504,68 @@ public:
return 0;
};
/// Set the mapped child elements.
void setMappedChildElements(const std::vector<Data::ElementMap::MappedChildElements>& children);
/// Get the mapped child elements.
std::vector<Data::ElementMap::MappedChildElements> 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<MappedElement> getElementMap() const;
/// Set the entire element map
/// Set the entire element map.
void setElementMap(const std::vector<MappedElement>& 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<IndexedName> 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<double>(vec.z));
// clang-format on
}
/// from local to outside
/// Transform the points from local to outside.
template<typename Vec>
inline std::vector<Base::Vector3d> transformPointsToOutside(const std::vector<Vec>& 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<double>(vec.z));
// clang-format on
}
/// Transform the vectors from local to outside.
template<typename Vec>
std::vector<Base::Vector3d> transformVectorsToOutside(const std::vector<Vec>& 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<double>(vec.x),
static_cast<double>(vec.y),
static_cast<double>(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<float>(tmp.x),
static_cast<float>(tmp.y),
static_cast<float>(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;
};

View File

@@ -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

View File

@@ -45,80 +45,108 @@ namespace Data
class ElementMap;
using ElementMapPtr = std::shared_ptr<ElementMap>;
/** 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<bool(const MappedName&, int, long, long)> 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<ElementMap> // 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<std::pair<MappedName, ElementIDRefs>> findAll(const IndexedName& idx) const;
// prefix searching is disabled, as TopoShape::getRelatedElement() is
@@ -166,16 +231,23 @@ public:
std::vector<MappedElement> 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<MappedChildElements>& children);
/// Get the child elements.
std::vector<MappedChildElements> getChildElements() const;
/// Get all mapped elements.
std::vector<MappedElement> 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<MappedName>* 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;

View File

@@ -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);

View File

@@ -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<const char*>& 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<const char*>& allowedNames = {},

View File

@@ -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;
};

View File

@@ -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<int>(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<int>(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,

View File

@@ -3,6 +3,10 @@
* @ingroup CORE
* @brief The part of FreeCAD that works without GUI (console or server mode)
*
* @htmlonly
* <div class="textblock">
* @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
* </div>
* @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
*
*/