[Toponaming] create ElementMap class (#9175)

* Copypaste ElementMap
* Add MappedNameRef
* Fix missing include
* Copypaste `findTagInElementName`
* fix error introduced _somewhere_
* refactor toponaming constants
* Move `findTagInElementName` in `MappedName`
* reintroduce workaround to compile ElementMap
* Added missing functions copied from complexgeodata
* fix last compile errors, reorder and format files
* remove recursive refs to ComplexGeoData
* Add more comments
* fixed comments and added tests
* added FIXME, make functions private, misc fixes
* Move static functions from complexGeoData to PostfixStringReferences. Rename to ElementNamingUtils
* Fix broken includes due to previous change
* Revert constants from string to const char*
* added childmap tests and made hasher public
* Make functions private
* Added remaining tests
* removed bool return from `erase` functions
* fix missing appexport

Co-authored-by: John Dupuy <jdupuy98@gmail.com>
This commit is contained in:
Pesc0
2023-06-15 16:05:24 +02:00
committed by GitHub
parent 32d8029780
commit 4a8d3853ba
26 changed files with 2450 additions and 218 deletions

View File

@@ -266,6 +266,7 @@ SET(FreeCADApp_CPP_SRCS
ColorModel.cpp
ComplexGeoData.cpp
ComplexGeoDataPyImp.cpp
ElementMap.cpp
Enumeration.cpp
IndexedName.cpp
MappedElement.cpp
@@ -274,6 +275,7 @@ SET(FreeCADApp_CPP_SRCS
MaterialPyImp.cpp
Metadata.cpp
MetadataPyImp.cpp
ElementNamingUtils.cpp
StringHasher.cpp
StringHasherPyImp.cpp
StringIDPyImp.cpp
@@ -295,6 +297,7 @@ SET(FreeCADApp_HPP_SRCS
MappedElement.h
Material.h
Metadata.h
ElementNamingUtils.h
StringHasher.h
)

View File

@@ -27,10 +27,10 @@
# include <cstdlib>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <boost/regex.hpp>
#include "ComplexGeoData.h"
#include <Base/BoundBox.h>
#include <Base/Placement.h>
#include <Base/Rotation.h>
@@ -166,102 +166,3 @@ bool ComplexGeoData::getCenterOfGravity(Base::Vector3d&) const
return false;
}
const std::string &ComplexGeoData::elementMapPrefix() {
static std::string prefix(";");
return prefix;
}
const char *ComplexGeoData::isMappedElement(const char *name) {
if(name && boost::starts_with(name,elementMapPrefix()))
return name+elementMapPrefix().size();
return nullptr;
}
std::string ComplexGeoData::newElementName(const char *name) {
if(!name)
return std::string();
const char *dot = strrchr(name,'.');
if(!dot || dot==name)
return name;
const char *c = dot-1;
for(;c!=name;--c) {
if(*c == '.') {
++c;
break;
}
}
if(isMappedElement(c))
return std::string(name,dot-name);
return name;
}
std::string ComplexGeoData::oldElementName(const char *name) {
if(!name)
return std::string();
const char *dot = strrchr(name,'.');
if(!dot || dot==name)
return name;
const char *c = dot-1;
for(;c!=name;--c) {
if(*c == '.') {
++c;
break;
}
}
if(isMappedElement(c))
return std::string(name,c-name)+(dot+1);
return name;
}
std::string ComplexGeoData::noElementName(const char *name) {
if(!name)
return std::string();
auto element = findElementName(name);
if(element)
return std::string(name,element-name);
return name;
}
const char *ComplexGeoData::findElementName(const char *subname) {
if(!subname || !subname[0] || isMappedElement(subname))
return subname;
const char *dot = strrchr(subname,'.');
if(!dot)
return subname;
const char *element = dot+1;
if(dot==subname || isMappedElement(element))
return element;
for(--dot;dot!=subname;--dot) {
if(*dot == '.') {
++dot;
break;
}
}
if(isMappedElement(dot))
return dot;
return element;
}
const std::string &ComplexGeoData::tagPostfix() {
static std::string postfix(elementMapPrefix() + ":T");
return postfix;
}
const std::string &ComplexGeoData::indexPostfix() {
static std::string postfix(elementMapPrefix() + ":I");
return postfix;
}
const std::string &ComplexGeoData::missingPrefix() {
static std::string prefix("?");
return prefix;
}
bool ComplexGeoData::hasMissingElement(const char *subname) {
if(!subname)
return false;
auto dot = strrchr(subname,'.');
if(dot)
subname = dot+1;
return boost::starts_with(subname,missingPrefix());
}

View File

@@ -164,41 +164,6 @@ public:
virtual bool getCenterOfGravity(Base::Vector3d& center) const;
//@}
/** @name Element name mapping */
//@{
/// Special prefix to mark the beginning of a mapped sub-element name
static const std::string &elementMapPrefix();
/// Special postfix to mark the following tag
static const std::string &tagPostfix();
/// Special postfix to mark the index of an array element
static const std::string &indexPostfix();
/// Special prefix to mark a missing element
static const std::string &missingPrefix();
/// Check if a subname contains missing element
static bool hasMissingElement(const char *subname);
/** Check if the name starts with elementMapPrefix()
*
* @param name: input name
* @return Returns the name stripped with elementMapPrefix(), or 0 if not
* start with the prefix
*/
static const char *isMappedElement(const char *name);
/// Strip out the trailing element name if there is mapped element name precedes it.
static std::string newElementName(const char *name);
/// Strip out the mapped element name if there is one.
static std::string oldElementName(const char *name);
/// Strip out the old and new element name if there is one.
static std::string noElementName(const char *name);
/// Find the start of an element name in a subname
static const char *findElementName(const char *subname);
static inline const char *hasMappedElementName(const char *subname) {
return isMappedElement(findElementName(subname));
}
//@}
protected:
/// from local to outside

View File

@@ -34,7 +34,7 @@
#include <Base/Writer.h>
#include "Application.h"
#include "ComplexGeoData.h"
#include "ElementNamingUtils.h"
#include "Document.h"
#include "DocumentObject.h"
#include "DocumentObjectExtension.h"
@@ -1094,7 +1094,7 @@ DocumentObject *DocumentObject::resolve(const char *subname,
// following it. So finding the last dot will give us the end of the last
// object name.
const char *dot=nullptr;
if(Data::ComplexGeoData::isMappedElement(subname) ||
if(Data::isMappedElement(subname) ||
!(dot=strrchr(subname,'.')) ||
dot == subname)
{
@@ -1117,7 +1117,7 @@ DocumentObject *DocumentObject::resolve(const char *subname,
if(!elementMapChecked) {
elementMapChecked = true;
const char *sub = dot==subname?dot:dot+1;
if(Data::ComplexGeoData::isMappedElement(sub)) {
if(Data::isMappedElement(sub)) {
lastDot = dot;
if(dot==subname)
break;

View File

@@ -26,7 +26,7 @@
#include <Base/Tools.h>
#include "Application.h"
#include "ComplexGeoData.h"
#include "ElementNamingUtils.h"
#include "Document.h"
#include "DocumentObserver.h"
#include "GeoFeature.h"
@@ -353,11 +353,11 @@ const std::string &SubObjectT::getSubName() const {
}
std::string SubObjectT::getSubNameNoElement() const {
return Data::ComplexGeoData::noElementName(subname.c_str());
return Data::noElementName(subname.c_str());
}
const char *SubObjectT::getElementName() const {
return Data::ComplexGeoData::findElementName(subname.c_str());
return Data::findElementName(subname.c_str());
}
std::string SubObjectT::getNewElementName() const {

1158
src/App/ElementMap.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -27,22 +27,264 @@
#define DATA_ELEMENTMAP_H
#include "FCGlobal.h"
#include "IndexedName.h"
namespace Data {
#include "Application.h"
#include "MappedElement.h"
#include "StringHasher.h"
static constexpr const char *POSTFIX_TAG = ";:H";
static constexpr const char *POSTFIX_DECIMAL_TAG = ";:T";
static constexpr const char *POSTFIX_EXTERNAL_TAG = ";:X";
static constexpr const char *POSTFIX_CHILD = ";:C";
static constexpr const char *POSTFIX_INDEX = ";:I";
static constexpr const char *POSTFIX_UPPER = ";:U";
static constexpr const char *POSTFIX_LOWER = ";:L";
static constexpr const char *POSTFIX_MOD = ";:M";
static constexpr const char *POSTFIX_GEN = ";:G";
static constexpr const char *POSTFIX_MODGEN = ";:MG";
static constexpr const char *POSTFIX_DUPLICATE = ";D";
#include <cstring>
#include <deque>
#include <functional>
#include <map>
#include <memory>
} // namespace data
#endif // DATA_ELEMENTMAP_H
namespace Data
{
class ElementMap;
typedef std::shared_ptr<ElementMap> ElementMapPtr;
/* 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.
*/
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.
*/
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 hasher passed
* as a parameter. Finally do this recursively for all childEelementMaps as well.
*
* @param hasher where all the StringID 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& hasher) 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 s: serialized stream
*/
void save(std::ostream& s) 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 hasher: where all the StringIDs are stored
* @param s: stream to deserialize
*/
ElementMapPtr restore(::App::StringHasherRef hasher, std::istream& s);
/** 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.
*
* 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.
*/
MappedName setElementName(const IndexedName& element,
const MappedName& name,
long masterTag,
const ElementIDRefs* sid = nullptr,
bool overwrite = false);
/* 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.
*/
void encodeElementName(char element_type,
MappedName& name,
std::ostringstream& ss,
ElementIDRefs* sids,
long masterTag,
const char* postfix = 0,
long tag = 0,
bool forceTag = false) const;
/// Remove \c name from the map
void erase(const MappedName& name);
/// Remove \c idx and all the MappedNames associated with it
void erase(const IndexedName& idx);
unsigned long size() const;
bool empty() const;
IndexedName find(const MappedName& name, ElementIDRefs* sids = nullptr) const;
MappedName find(const IndexedName& idx, ElementIDRefs* sids = nullptr) const;
std::vector<std::pair<MappedName, ElementIDRefs>> findAll(const IndexedName& idx) const;
// prefix searching is disabled, as TopoShape::getRelatedElement() is
// deprecated in favor of GeoFeature::getRelatedElement(). Besides, there
// is efficient way to support child element map if we were to implement
// prefix search.
#if 0
std::vector<MappedElement> findAllStartsWith(const char *prefix) const;
#endif
bool hasChildElementMap() const;
/* Ensures that for each IndexedName mapped to IndexedElements, that
* each child is properly hashed (cached).
*
* Note: the original proc was in the context of ComplexGeoData, which provided `Tag` access,
* now you must pass in `long masterTag` explicitly.
*/
void hashChildMaps(long masterTag);
struct AppExport MappedChildElements
{
IndexedName indexedName;
int count;
int offset;
long tag;
ElementMapPtr elementMap;
QByteArray postfix;
ElementIDRefs sids;
// prefix() has been moved to PostfixStringReferences.h
};
/* 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);
std::vector<MappedChildElements> getChildElements() const;
std::vector<MappedElement> getAll() const;
private:
/** Serialize this map
* @param s: serialized stream
* @param childMapSet: where all child element maps are stored
* @param postfixMap. where all postfixes are stored
*/
void save(std::ostream& s, int index, const std::map<const ElementMap*, int>& childMapSet,
const std::map<QByteArray, int>& postfixMap) const;
/** Deserialize and restore this map.
* @param hasher: where all the StringIDs are stored
* @param s: stream to deserialize
* @param childMaps: where all child element maps are stored
* @param postfixes. where all postfixes are stored
*/
ElementMapPtr restore(::App::StringHasherRef hasher, std::istream& s,
std::vector<ElementMapPtr>& childMaps,
const std::vector<std::string>& postfixes);
/** Associate the MappedName \c name with the IndexedName \c idx.
* @param name: the name to add
* @param idx: the indexed name that \c name will be bound to
* @param sids: where StringIDs that make up the name are stored
* @param overwrite: if true, all the names associated with \c idx will be discarded
* @param existing: out variable: if not overwriting, and \c name is already
* associated with another indexedName, set \c existing to that indexedname
* @return the name just added, or an empty name if it wasn't added.
*/
MappedName addName(MappedName& name, const IndexedName& idx, const ElementIDRefs& sids,
bool overwrite, IndexedName* existing);
/** Utility function that adds \c postfix to \c postfixMap, and to \c postfixes
* if it was not present in the map.
*/
static void addPostfix(const QByteArray& postfix, std::map<QByteArray, int>& postfixMap,
std::vector<QByteArray>& postfixes);
/* Note: the original proc passed `ComplexGeoData& master` for getting the `Tag`,
* now it just passes `long masterTag`.*/
virtual MappedName renameDuplicateElement(int index, const IndexedName& element,
const IndexedName& element2, const MappedName& name,
ElementIDRefs& sids, long masterTag);
/** Convenience method to hash the main element name
*
* @param name: main element name
* @param sid: store any output string ID references
* @return the hashed element name;
*/
MappedName hashElementName(const MappedName& name, ElementIDRefs& sids) const;
/// Reverse hashElementName()
MappedName dehashElementName(const MappedName& name) const;
//FIXME duplicate code? as in copy/paste
const MappedNameRef* findMappedRef(const IndexedName& idx) const;
MappedNameRef* findMappedRef(const IndexedName& idx);
MappedNameRef& mappedRef(const IndexedName& idx);
void collectChildMaps(std::map<const ElementMap*, int>& childMapSet,
std::vector<const ElementMap*>& childMaps,
std::map<QByteArray, int>& postfixMap,
std::vector<QByteArray>& postfixes) const;
struct CStringComp
{
public:
bool operator()(const char* str1, const char* str2) const
{
return std::strcmp(str1, str2) < 0;
}
};
struct IndexedElements
{
std::deque<MappedNameRef> names;
std::map<int, MappedChildElements> children;
};
std::map<const char*, IndexedElements, CStringComp> indexedNames;
std::map<MappedName, IndexedName, std::less<MappedName>> mappedNames;
struct ChildMapInfo
{
int index = 0;
MappedChildElements* childMap = nullptr;
std::map<ElementMap*, int> mapIndices;
};
QHash<QByteArray, ChildMapInfo> childElements;
std::size_t childElementSize = 0;
mutable unsigned _id = 0;
void init();
public:
/// String hasher for element name shortening
App::StringHasherRef hasher;
};
}// namespace Data
#endif// DATA_ELEMENTMAP_H

View File

@@ -0,0 +1,88 @@
#include "ElementNamingUtils.h"
#include <boost/algorithm/string/predicate.hpp>
const char *Data::isMappedElement(const char *name) {
if(name && boost::starts_with(name, ELEMENT_MAP_PREFIX))
return name + ELEMENT_MAP_PREFIX_SIZE;
return nullptr;
}
std::string Data::newElementName(const char *name) {
if(!name)
return std::string();
const char *dot = strrchr(name,'.');
if(!dot || dot==name)
return name;
const char *c = dot-1;
for(;c!=name;--c) {
if(*c == '.') {
++c;
break;
}
}
if(isMappedElement(c))
return std::string(name,dot-name);
return name;
}
std::string Data::oldElementName(const char *name) {
if(!name)
return std::string();
const char *dot = strrchr(name,'.');
if(!dot || dot==name)
return name;
const char *c = dot-1;
for(;c!=name;--c) {
if(*c == '.') {
++c;
break;
}
}
if(isMappedElement(c))
return std::string(name,c-name)+(dot+1);
return name;
}
std::string Data::noElementName(const char *name) {
if(!name)
return std::string();
auto element = findElementName(name);
if(element)
return std::string(name,element-name);
return name;
}
const char *Data::findElementName(const char *subname) {
if(!subname || !subname[0] || isMappedElement(subname))
return subname;
const char *dot = strrchr(subname,'.');
if(!dot)
return subname;
const char *element = dot+1;
if(dot==subname || isMappedElement(element))
return element;
for(--dot;dot!=subname;--dot) {
if(*dot == '.') {
++dot;
break;
}
}
if(isMappedElement(dot))
return dot;
return element;
}
bool Data::hasMissingElement(const char *subname) {
if(!subname)
return false;
auto dot = strrchr(subname,'.');
if(dot)
subname = dot+1;
return boost::starts_with(subname, MISSING_PREFIX);
}
const char *Data::hasMappedElementName(const char *subname) {
return isMappedElement(findElementName(subname));
}

View File

@@ -0,0 +1,66 @@
#pragma once
#include <string>
#include "FCGlobal.h"
namespace Data
{
/// Special prefix to mark the beginning of a mapped sub-element name
constexpr const char* ELEMENT_MAP_PREFIX = ";";
constexpr size_t ELEMENT_MAP_PREFIX_SIZE = 1;
/// Special prefix to mark a missing element
constexpr const char* MISSING_PREFIX = "?";
// IMPORTANT: For all the constants below, the semicolon ";"
// at the start is ELEMENT_MAP_PREFIX
constexpr const char* MAPPED_CHILD_ELEMENTS_PREFIX = ";:R";
/// Special postfix to mark the following tag
constexpr const char* POSTFIX_TAG = ";:H";
constexpr size_t POSTFIX_TAG_SIZE = 3;
constexpr const char* POSTFIX_DECIMAL_TAG = ";:T";
constexpr const char* POSTFIX_EXTERNAL_TAG = ";:X";
constexpr const char* POSTFIX_CHILD = ";:C";
/// Special postfix to mark the index of an array element
constexpr const char* POSTFIX_INDEX = ";:I";
constexpr const char* POSTFIX_UPPER = ";:U";
constexpr const char* POSTFIX_LOWER = ";:L";
constexpr const char* POSTFIX_MOD = ";:M";
constexpr const char* POSTFIX_GEN = ";:G";
constexpr const char* POSTFIX_MODGEN = ";:MG";
constexpr const char* POSTFIX_DUPLICATE = ";D";
/// Check if a subname contains missing element
AppExport bool hasMissingElement(const char *subname);
/** Check if the name starts with elementMapPrefix()
*
* @param name: input name
* @return Returns the name stripped with elementMapPrefix(), or 0 if not
* start with the prefix
*/
AppExport const char *isMappedElement(const char *name);
/// Strip out the trailing element name if there is mapped element name preceeds it.
AppExport std::string newElementName(const char *name);
/// Strip out the mapped element name if there is one.
AppExport std::string oldElementName(const char *name);
/// Strip out the old and new element name if there is one.
AppExport std::string noElementName(const char *name);
/// Find the start of an element name in a subname
AppExport const char *findElementName(const char *subname);
AppExport const char *hasMappedElementName(const char *subname);
}// namespace Data

View File

@@ -27,7 +27,7 @@
#include "GeoFeature.h"
#include "GeoFeatureGroupExtension.h"
#include "ComplexGeoData.h"
#include "ElementNamingUtils.h"
using namespace App;
@@ -101,7 +101,7 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn
return nullptr;
if(!subname)
subname = "";
const char *element = Data::ComplexGeoData::findElementName(subname);
const char *element = Data::findElementName(subname);
if(_element) *_element = element;
auto sobj = obj->getSubObject(subname);
if(!sobj)
@@ -114,7 +114,7 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn
return nullptr;
if(!element || !element[0]) {
if(append)
elementName.second = Data::ComplexGeoData::oldElementName(subname);
elementName.second = Data::oldElementName(subname);
return sobj;
}
@@ -122,7 +122,7 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn
if(!append)
elementName.second = element;
else
elementName.second = Data::ComplexGeoData::oldElementName(subname);
elementName.second = Data::oldElementName(subname);
return sobj;
}
if(!append)

View File

@@ -29,7 +29,7 @@
#include <Base/Uuid.h>
#include "Application.h"
#include "ComplexGeoData.h"
#include "ElementNamingUtils.h"
#include "ComplexGeoDataPy.h"
#include "Document.h"
#include "DocumentObserver.h"
@@ -1051,7 +1051,7 @@ DocumentObject *LinkBaseExtension::getLink(int depth) const{
}
int LinkBaseExtension::getArrayIndex(const char *subname, const char **psubname) {
if(!subname || Data::ComplexGeoData::isMappedElement(subname))
if(!subname || Data::isMappedElement(subname))
return -1;
const char *dot = strchr(subname,'.');
if(!dot) dot= subname+strlen(subname);
@@ -1073,7 +1073,7 @@ int LinkBaseExtension::getArrayIndex(const char *subname, const char **psubname)
}
int LinkBaseExtension::getElementIndex(const char *subname, const char **psubname) const {
if(!subname || Data::ComplexGeoData::isMappedElement(subname))
if(!subname || Data::isMappedElement(subname))
return -1;
int idx = -1;
const char *dot = strchr(subname,'.');
@@ -1313,7 +1313,7 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char *
return true;
ret = elements[idx]->getSubObject(subname,pyObj,mat,true,depth+1);
// do not resolve the link if this element is the last referenced object
if(!subname || Data::ComplexGeoData::isMappedElement(subname) || !strchr(subname,'.'))
if(!subname || Data::isMappedElement(subname) || !strchr(subname,'.'))
ret = elements[idx];
return true;
}
@@ -1381,7 +1381,7 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char *
std::string postfix;
if(ret) {
// do not resolve the link if we are the last referenced object
if(subname && !Data::ComplexGeoData::isMappedElement(subname) && strchr(subname,'.')) {
if(subname && !Data::isMappedElement(subname) && strchr(subname,'.')) {
if(mat)
*mat = matNext;
}
@@ -1394,7 +1394,7 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char *
}
else {
if(idx) {
postfix = Data::ComplexGeoData::indexPostfix();
postfix = Data::POSTFIX_INDEX;
postfix += std::to_string(idx);
}
if(mat)
@@ -1488,7 +1488,7 @@ void LinkBaseExtension::parseSubName() const {
}
const auto &subs = xlink->getSubValues();
auto subname = subs.front().c_str();
auto element = Data::ComplexGeoData::findElementName(subname);
auto element = Data::findElementName(subname);
if(!element || !element[0]) {
mySubName = subs[0];
if(hasSubElement)
@@ -1499,7 +1499,7 @@ void LinkBaseExtension::parseSubName() const {
mySubName = std::string(subname,element-subname);
for(std::size_t i=1;i<subs.size();++i) {
auto &sub = subs[i];
element = Data::ComplexGeoData::findElementName(sub.c_str());
element = Data::findElementName(sub.c_str());
if(element && element[0] && boost::starts_with(sub,mySubName))
mySubElements.emplace_back(element);
}
@@ -1938,7 +1938,7 @@ void LinkBaseExtension::onExtendedDocumentRestored() {
} else {
std::set<std::string> subset(mySubElements.begin(),mySubElements.end());
auto sub = xlink->getSubValues().front();
auto element = Data::ComplexGeoData::findElementName(sub.c_str());
auto element = Data::findElementName(sub.c_str());
if(element && element[0]) {
subset.insert(element);
sub.resize(element - sub.c_str());

View File

@@ -26,19 +26,26 @@
# include <unordered_set>
#endif
//#include <boost/functional/hash.hpp>
#include "MappedName.h"
using namespace Data;
#include "Base/Console.h"
//#include <boost/functional/hash.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
void MappedName::compact()
FC_LOG_LEVEL_INIT("MappedName", true, 2);
namespace Data {
void MappedName::compact() const
{
auto self = const_cast<MappedName*>(this); //FIXME this is a workaround for a single call in ElementMap::addName()
if (this->raw) {
this->data = QByteArray(this->data.constData(), this->data.size());
this->raw = false;
self->data = QByteArray(self->data.constData(), self->data.size());
self->raw = false;
}
#if 0
@@ -51,3 +58,158 @@ void MappedName::compact()
#endif
}
int MappedName::findTagInElementName(long* tag, int* len, const char* postfix,
char* type, bool negative, bool recursive) const
{
bool hex = true;
int pos = this->rfind(POSTFIX_TAG);
// Example name, tagPosfix == ;:H
// #94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F
// ^
// |
// pos
if(pos < 0) {
pos = this->rfind(POSTFIX_DECIMAL_TAG);
if (pos < 0)
return -1;
hex = false;
}
int offset = pos + (int)POSTFIX_TAG_SIZE;
long _tag = 0;
int _len = 0;
char sep = 0;
char sep2 = 0;
char tp = 0;
char eof = 0;
int size;
const char *s = this->toConstString(offset, size);
// check if the number followed by the tagPosfix is negative
bool isNegative = (s[0] == '-');
if (isNegative) {
++s;
--size;
}
boost::iostreams::stream<boost::iostreams::array_source> iss(s, size);
if (!hex) {
// no hex is an older version of the encoding scheme
iss >> _tag >> sep;
} else {
// The purpose of tag postfix is to encode one model operation. The
// 'tag' field is used to record the own object ID of that model shape,
// and the 'len' field indicates the length of the operation codes
// before the tag postfix. These fields are in hex. The trailing 'F' is
// the shape type of this element, 'F' for face, 'E' edge, and 'V' vertex.
//
// #94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F
// | | ^^ ^^
// | | | |
// ---len = 0x10--- tag len
iss >> std::hex;
// _tag field can be skipped, if it is 0
if (s[0] == ',' || s[0] == ':')
iss >> sep;
else
iss >> _tag >> sep;
}
if (isNegative)
_tag = -_tag;
if (sep == ':') {
// ':' is followed by _len field.
//
// For decTagPostfix() (i.e. older encoding scheme), this is the length
// of the string before the entire postfix (A postfix may contain
// multiple segments usually separated by ELEMENT_MAP_PREFIX.
//
// For newer POSTFIX_TAG, this counts the number of characters that
// proceeds this tag postfix segment that forms the op code (see
// example above).
//
// The reason of this change is so that the postfix can stay the same
// regardless of the prefix, which can increase memory efficiency.
//
iss >> _len >> sep2 >> tp >> eof;
// The next separator to look for is either ':' for older tag postfix, or ','
if (!hex && sep2 == ':')
sep2 = ',';
}
else if (hex && sep == ',') {
// ',' is followed by a single character that indicates the element type.
iss >> tp >> eof;
sep = ':';
sep2 = ',';
}
if (_len < 0 || sep != ':' || sep2 != ',' || tp == 0 || eof != 0)
return -1;
if (hex) {
if (pos-_len < 0)
return -1;
if (_len && recursive && (tag || len)) {
// in case of recursive tag postfix (used by hierarchy element
// map), look for any embedded tag postifx
int next = MappedName::fromRawData(*this, pos-_len, _len).rfind(POSTFIX_TAG);
if (next >= 0) {
next += pos - _len;
// #94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F
// ^ ^
// | |
// next pos
//
// There maybe other operation codes after this embedded tag
// postfix, search for the sperator.
//
int end;
if (pos == next)
end = -1;
else
end = MappedName::fromRawData(*this, next+1, pos-next-1).find(ELEMENT_MAP_PREFIX);
if (end >= 0) {
end += next+1;
// #94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F
// ^
// |
// end
_len = pos - end;
// #94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F
// | |
// -- len --
} else
_len = 0;
}
}
// Now convert the 'len' field back to the length of the remaining name
//
// #94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F
// | |
// ----------- len -----------
_len = pos - _len;
}
if(type)
*type = tp;
if(tag) {
if (_tag == 0 && recursive)
return MappedName(*this, 0, _len).findTagInElementName(tag, len, postfix, type, negative);
if(_tag>0 || negative)
*tag = _tag;
else
*tag = -_tag;
}
if(len)
*len = _len;
if(postfix)
this->toString(*postfix, pos);
return pos;
}
}

View File

@@ -26,15 +26,19 @@
#define APP_MAPPED_NAME_H
#include <memory>
#include <string>
#include <boost/algorithm/string/predicate.hpp>
#include <QByteArray>
#include <QHash>
#include <QVector>
#include "ComplexGeoData.h"
#include "IndexedName.h"
#include "StringHasher.h"
#include "ElementNamingUtils.h"
namespace Data
@@ -62,8 +66,8 @@ public:
if (!name) {
return;
}
if (boost::starts_with(name, ComplexGeoData::elementMapPrefix())) {
name += ComplexGeoData::elementMapPrefix().size();
if (boost::starts_with(name, ELEMENT_MAP_PREFIX)) {
name += ELEMENT_MAP_PREFIX_SIZE;
}
data = size < 0 ? QByteArray(name) : QByteArray(name, size);
@@ -78,9 +82,9 @@ public:
{
auto size = nameString.size();
const char* name = nameString.c_str();
if (boost::starts_with(nameString, ComplexGeoData::elementMapPrefix())) {
name += ComplexGeoData::elementMapPrefix().size();
size -= ComplexGeoData::elementMapPrefix().size();
if (boost::starts_with(nameString, ELEMENT_MAP_PREFIX)) {
name += ELEMENT_MAP_PREFIX_SIZE;
size -= ELEMENT_MAP_PREFIX_SIZE;
}
data = QByteArray(name, static_cast<int>(size));
}
@@ -622,7 +626,7 @@ public:
const char* appendToBufferWithPrefix(std::string& buf) const
{
if (!toIndexedName()) {
buf += ComplexGeoData::elementMapPrefix();
buf += ELEMENT_MAP_PREFIX;
}
appendToBuffer(buf);
return buf.c_str();
@@ -714,7 +718,7 @@ public:
}
/// Ensure that this data is unshared, making a copy if necessary.
void compact();
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.
@@ -786,8 +790,7 @@ public:
if (!searchTarget) {
return -1;
}
if (startPosition < 0
|| startPosition >= this->data.size()) {
if (startPosition < 0 || startPosition >= this->data.size()) {
if (startPosition >= data.size()) {
startPosition -= data.size();
}
@@ -887,6 +890,22 @@ public:
offset);
}
/// Extract tag and other information from a encoded element name
///
/// \param tag: optional pointer to receive the extracted tag
/// \param len: optional pointer to receive the length field after the tag field.
/// This gives the length of the previous hashsed element name starting
/// from the beginning of the give element name.
/// \param postfix: optional pointer to receive the postfix starting at the found tag field.
/// \param type: optional pointer to receive the element type character
/// \param negative: return negative tag as it is. If disabled, then always return positive tag.
/// Negative tag is sometimes used for element disambiguation.
/// \param recursive: recursively find the last non-zero tag
///
/// \return Return the end position of the tag field, or return -1 if not found.
int findTagInElementName(long* tag = 0, int* len = 0, const char* postfix = 0, char* type = 0,
bool negative = false, bool recursive = true) const;
/// Get a hash for this MappedName
std::size_t hash() const
{
@@ -899,6 +918,110 @@ private:
bool raw;
};
typedef QVector<::App::StringIDRef> ElementIDRefs;
struct MappedNameRef
{
MappedName name;
ElementIDRefs sids;
std::unique_ptr<MappedNameRef> next;
MappedNameRef() = default;
MappedNameRef(const MappedName& name, const ElementIDRefs& sids = ElementIDRefs())
: name(name),
sids(sids)
{
compact();
}
MappedNameRef(const MappedNameRef& other)
: name(other.name),
sids(other.sids)
{}
MappedNameRef(MappedNameRef&& other)
: name(std::move(other.name)),
sids(std::move(other.sids)),
next(std::move(other.next))
{}
MappedNameRef& operator=(MappedNameRef&& other)
{
name = std::move(other.name);
sids = std::move(other.sids);
next = std::move(other.next);
return *this;
}
explicit operator bool() const
{
return !name.empty();
}
void append(const MappedName& name, const ElementIDRefs sids = ElementIDRefs())
{
if (!name)
return;
if (!this->name) {
this->name = name;
this->sids = sids;
compact();
return;
}
std::unique_ptr<MappedNameRef> n(new MappedNameRef(name, sids));
if (!this->next)
this->next = std::move(n);
else {
this->next.swap(n);
this->next->next = std::move(n);
}
}
void compact()
{
if (sids.size() > 1) {
std::sort(sids.begin(), sids.end());
sids.erase(std::unique(sids.begin(), sids.end()), sids.end());
}
}
bool erase(const MappedName& name)
{
if (this->name == name) {
this->name.clear();
this->sids.clear();
if (this->next) {
this->name = std::move(this->next->name);
this->sids = std::move(this->next->sids);
std::unique_ptr<MappedNameRef> tmp;
tmp.swap(this->next);
this->next = std::move(tmp->next);
}
return true;
}
for (std::unique_ptr<MappedNameRef>* p = &this->next; *p; p = &(*p)->next) {
if ((*p)->name == name) {
std::unique_ptr<MappedNameRef> tmp;
tmp.swap(*p);
*p = std::move(tmp->next);
return true;
}
}
return false;
}
void clear()
{
this->name.clear();
this->sids.clear();
this->next.reset();
}
};
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)

View File

@@ -30,6 +30,7 @@
#include <sstream>
#include <vector>
#include <cassert>
#include <memory>
#ifdef _MSC_VER
#include <zipios++/zipios-config.h>

View File

@@ -28,7 +28,7 @@
#endif
#include <App/Application.h>
#include <App/ComplexGeoData.h>
#include <App/ElementNamingUtils.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/Link.h>
@@ -294,8 +294,8 @@ void StdCmdLinkMakeRelative::activated(int) {
if(!sel.pObject || !sel.pObject->getNameInDocument())
continue;
auto key = std::make_pair(sel.pObject,
Data::ComplexGeoData::noElementName(sel.SubName));
auto element = Data::ComplexGeoData::findElementName(sel.SubName);
Data::noElementName(sel.SubName));
auto element = Data::findElementName(sel.SubName);
auto &info = linkInfo[key];
info.first = sel.pResolvedObject;
if(element && element[0])

View File

@@ -36,11 +36,11 @@
#endif
#include <App/AutoTransaction.h>
#include <App/ComplexGeoData.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObjectGroup.h>
#include <App/Transactions.h>
#include <App/ElementNamingUtils.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Matrix.h>
@@ -408,7 +408,7 @@ bool Document::setEdit(Gui::ViewProvider* p, int ModNum, const char *subname)
d->_editSubname.clear();
if (subname) {
const char *element = Data::ComplexGeoData::findElementName(subname);
const char *element = Data::findElementName(subname);
if (element) {
d->_editSubname = std::string(subname,element-subname);
d->_editSubElement = element;

View File

@@ -71,7 +71,7 @@
#endif
#include <App/Document.h>
#include <App/ComplexGeoData.h>
#include <App/ElementNamingUtils.h>
#include <Base/Tools.h>
#include "SoFCUnifiedSelection.h"
@@ -624,7 +624,7 @@ bool SoFCUnifiedSelection::setSelection(const std::vector<PickedInfo> &infos, bo
objectName << ", " << subName);
std::string newElement;
if(subSelected) {
newElement = Data::ComplexGeoData::newElementName(subSelected);
newElement = Data::newElementName(subSelected);
subSelected = newElement.c_str();
std::string nextsub;
const char *next = strrchr(subSelected,'.');

View File

@@ -28,7 +28,7 @@
# include <sstream>
#endif
#include <App/ComplexGeoData.h>
#include <App/ElementNamingUtils.h>
#include <App/Document.h>
#include "TaskElementColors.h"
@@ -84,7 +84,7 @@ public:
auto obj = vpParent->getObject();
editDoc = obj->getDocument()->getName();
editObj = obj->getNameInDocument();
editSub = Data::ComplexGeoData::noElementName(editSub.c_str());
editSub = Data::noElementName(editSub.c_str());
}
}
if(editDoc.empty()) {
@@ -162,7 +162,7 @@ public:
c.setRgbF(color.r,color.g,color.b,1.0-color.a);
px.fill(c);
auto item = new QListWidgetItem(QIcon(px),
QString::fromLatin1(Data::ComplexGeoData::oldElementName(v.first.c_str()).c_str()),
QString::fromLatin1(Data::oldElementName(v.first.c_str()).c_str()),
ui->elementList);
item->setData(Qt::UserRole,c);
item->setData(Qt::UserRole+1,QString::fromLatin1(v.first.c_str()));
@@ -419,7 +419,7 @@ void ElementColors::onHideSelectionClicked() {
if(!subs.empty()) {
for(auto &sub : subs) {
if(boost::starts_with(sub,d->editSub)) {
auto name = Data::ComplexGeoData::noElementName(sub.c_str()+d->editSub.size());
auto name = Data::noElementName(sub.c_str()+d->editSub.size());
name += ViewProvider::hiddenMarker();
d->addItem(-1,name.c_str());
}

View File

@@ -48,7 +48,7 @@
#endif
#include <boost/range.hpp>
#include <App/ComplexGeoData.h>
#include <App/ElementNamingUtils.h>
#include <App/Document.h>
#include <Base/BoundBoxPy.h>
#include <Base/MatrixPy.h>
@@ -635,7 +635,7 @@ public:
break;
}
// new style mapped sub-element
if(Data::ComplexGeoData::isMappedElement(dot+1))
if(Data::isMappedElement(dot+1))
break;
auto next = strchr(dot+1,'.');
if(!next) {
@@ -1031,7 +1031,7 @@ void LinkView::setLinkViewObject(ViewProviderDocumentObject *vpd,
subInfo.clear();
for(const auto &sub : subs) {
if(sub.empty()) continue;
const char *subelement = Data::ComplexGeoData::findElementName(sub.c_str());
const char *subelement = Data::findElementName(sub.c_str());
std::string subname = sub.substr(0,subelement-sub.c_str());
auto it = subInfo.find(subname);
if(it == subInfo.end()) {

View File

@@ -74,6 +74,7 @@
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObjectPy.h>
#include <App/ElementNamingUtils.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/FileInfo.h>
@@ -102,7 +103,6 @@
#include "TopoShapeSolidPy.h"
#include "TopoShapeWirePy.h"
#ifdef FCUseFreeType
# include "FT2FC.h"
#endif
@@ -2286,14 +2286,14 @@ private:
const char *subname;
if (!PyArg_ParseTuple(args.ptr(), "s",&subname))
throw Py::Exception();
auto element = Data::ComplexGeoData::findElementName(subname);
auto element = Data::findElementName(subname);
std::string sub(subname,element-subname);
Py::List list;
list.append(Py::String(sub));
const char *dot = strchr(element,'.');
if(!dot)
dot = element+strlen(element);
const char *mapped = Data::ComplexGeoData::isMappedElement(element);
const char *mapped = Data::isMappedElement(element);
if(mapped)
list.append(Py::String(std::string(mapped,dot-mapped)));
else
@@ -2317,8 +2317,8 @@ private:
if (!subname.empty() && subname[subname.size()-1]!='.')
subname += '.';
if (mapped && mapped[0]) {
if (!Data::ComplexGeoData::isMappedElement(mapped))
subname += Data::ComplexGeoData::elementMapPrefix();
if (!Data::isMappedElement(mapped))
subname += Data::ELEMENT_MAP_PREFIX;
subname += mapped;
}
if (element && element[0]) {

View File

@@ -55,6 +55,7 @@
#include <App/FeaturePythonPyImp.h>
#include <App/Link.h>
#include <App/GeoFeatureGroupExtension.h>
#include <App/ElementNamingUtils.h>
#include <Base/Exception.h>
#include <Base/Placement.h>
#include <Base/Rotation.h>
@@ -65,7 +66,6 @@
#include "PartPyCXX.h"
#include "TopoShapePy.h"
using namespace Part;
namespace bp = boost::placeholders;
@@ -121,7 +121,7 @@ App::DocumentObject *Feature::getSubObject(const char *subname,
{
// having '.' inside subname means it is referencing some children object,
// instead of any sub-element from ourself
if(subname && !Data::ComplexGeoData::isMappedElement(subname) && strchr(subname,'.'))
if(subname && !Data::isMappedElement(subname) && strchr(subname,'.'))
return App::DocumentObject::getSubObject(subname,pyObj,pmat,transform,depth);
Base::Matrix4D _mat;
@@ -280,7 +280,7 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna
if(powner) *powner = nullptr;
std::string _subname;
auto subelement = Data::ComplexGeoData::findElementName(subname);
auto subelement = Data::findElementName(subname);
if(!needSubElement && subname) {
// strip out element name if not needed
if(subelement && *subelement) {
@@ -413,7 +413,7 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna
continue;
}else{
if(link && !link->getShowElementValue())
shape = baseShape.makeTransform(mat,(TopoShape::indexPostfix()+childName).c_str());
shape = baseShape.makeTransform(mat,(Data::POSTFIX_INDEX + childName).c_str());
else {
shape = baseShape.makeTransform(mat);
}

View File

@@ -160,6 +160,7 @@
#endif // _PreComp_
#include <App/Material.h>
#include <App/ElementNamingUtils.h>
#include <Base/BoundBox.h>
#include <Base/Builder3D.h>
#include <Base/Console.h>
@@ -472,7 +473,7 @@ TopAbs_ShapeEnum TopoShape::shapeType(const char *type, bool silent) {
}
}
if(!silent) {
if(Data::ComplexGeoData::hasMissingElement(type))
if(Data::hasMissingElement(type))
FC_THROWM(Base::CADKernelError,"missing shape element: " << (type?type:"?"));
FC_THROWM(Base::CADKernelError,"invalid shape type: " << (type?type:"?"));
}

View File

@@ -34,6 +34,7 @@
#include "App/DocumentObject.h"
#include <App/FeaturePythonPyImp.h>
#include <App/ElementNamingUtils.h>
#include "App/OriginFeature.h"
#include <Base/Console.h>
@@ -237,7 +238,7 @@ Body* Feature::getFeatureBody() const {
App::DocumentObject *Feature::getSubObject(const char *subname,
PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const
{
if (subname && subname != Data::ComplexGeoData::findElementName(subname)) {
if (subname && subname != Data::findElementName(subname)) {
const char * dot = strchr(subname,'.');
if (dot) {
auto body = PartDesign::Body::findBodyOf(this);

View File

@@ -39,6 +39,7 @@
#include <App/GroupExtension.h>
#include <App/Link.h>
#include <App/OriginFeature.h>
#include <App/ElementNamingUtils.h>
#include <Mod/Part/App/TopoShape.h>
#include "ShapeBinder.h"
@@ -382,7 +383,7 @@ App::DocumentObject* SubShapeBinder::getSubObject(const char* subname, PyObject*
auto sobj = Part::Feature::getSubObject(subname, pyObj, mat, transform, depth);
if (sobj)
return sobj;
if (Data::ComplexGeoData::findElementName(subname) == subname)
if (Data::findElementName(subname) == subname)
return nullptr;
const char* dot = strchr(subname, '.');
@@ -405,7 +406,7 @@ App::DocumentObject* SubShapeBinder::getSubObject(const char* subname, PyObject*
}
else if (!boost::equals(sobj->getNameInDocument(), name))
continue;
name = Data::ComplexGeoData::noElementName(sub.c_str());
name = Data::noElementName(sub.c_str());
name += dot + 1;
if (mat && transform)
*mat *= Placement.getValue().toMatrix();
@@ -640,7 +641,7 @@ void SubShapeBinder::update(SubShapeBinder::UpdateOption options) {
std::ostringstream ss;
ss << "Failed to obtain shape " <<
obj->getFullName() << '.'
<< Data::ComplexGeoData::oldElementName(sub.c_str());
<< Data::oldElementName(sub.c_str());
errMsg = ss.str();
}
}

View File

@@ -2,17 +2,537 @@
#include "gtest/gtest.h"
#include "App/ElementMap.h"
#include <App/Application.h>
#include <App/ElementMap.h>
#include <sstream>
// NOLINTBEGIN(readability-magic-numbers)
// this is a "holder" class used for simpler testing of ElementMap in the context of a class
class LessComplexPart
{
public:
LessComplexPart(long tag, const std::string& nameStr, App::StringHasherRef hasher)
: elementMapPtr(std::make_shared<Data::ElementMap>())
, Tag(tag)
, name(nameStr)
{
// object also have Vertexes etc and the face count varies; but that is not important
// here since we are not testing a real model
// the "MappedName" is left blank for now
Data::IndexedName face1("Face", 1);
Data::IndexedName face2("Face", 2);
Data::IndexedName face3("Face", 3);
Data::IndexedName face4("Face", 4);
Data::IndexedName face5("Face", 5);
Data::IndexedName face6("Face", 6);
elementMapPtr->hasher = hasher;
elementMapPtr->setElementName(face1, Data::MappedName(face1), Tag);
elementMapPtr->setElementName(face2, Data::MappedName(face2), Tag);
elementMapPtr->setElementName(face3, Data::MappedName(face3), Tag);
elementMapPtr->setElementName(face4, Data::MappedName(face4), Tag);
elementMapPtr->setElementName(face5, Data::MappedName(face5), Tag);
elementMapPtr->setElementName(face6, Data::MappedName(face6), Tag);
}
Data::ElementMapPtr elementMapPtr;
mutable long Tag;
Data::MappedName name;
};
class ElementMapTest: public ::testing::Test
{
protected:
static void SetUpTestSuite()
{
int argc = 1;
char* argv[] = {"FreeCAD"};
App::Application::Config()["ExeName"] = "FreeCAD";
App::Application::init(argc, argv);
}
void SetUp() override
{
App::GetApplication().newDocument("test", "testUser");
_sids = &_sid;
_hasher = Base::Reference<App::StringHasher>(new App::StringHasher);
}
// void TearDown() override {}
Data::ElementIDRefs _sid;
QVector<App::StringIDRef>* _sids;
App::StringHasherRef _hasher;
};
TEST_F(ElementMapTest, defaultConstruction)
{
// Act
Data::ElementMap elementMap = Data::ElementMap();
// Assert
EXPECT_EQ(elementMap.size(), 0);
}
TEST_F(ElementMapTest, setElementNameDefaults)
{
// Arrange
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
// Act
auto resultName = elementMap.setElementName(element, mappedName, 0);
auto mappedToElement = elementMap.find(element);
// Assert
EXPECT_EQ(resultName, mappedName);
EXPECT_EQ(mappedToElement, mappedName);
}
TEST_F(ElementMapTest, setElementNameNoOverwrite)
{
// Arrange
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
Data::MappedName anotherMappedName("ANOTHERTEST");
// Act
auto resultName = elementMap.setElementName(element, mappedName, 0);
auto resultName2 = elementMap.setElementName(element, anotherMappedName, 0, _sids, false);
auto mappedToElement = elementMap.find(element);
auto findAllResult = elementMap.findAll(element);
// Assert
EXPECT_EQ(resultName, mappedName);
EXPECT_EQ(resultName2, anotherMappedName);
EXPECT_EQ(mappedToElement, mappedName);
EXPECT_EQ(findAllResult.size(), 2);
EXPECT_EQ(findAllResult[0].first, mappedName);
EXPECT_EQ(findAllResult[1].first, anotherMappedName);
}
TEST_F(ElementMapTest, setElementNameWithOverwrite)
{
// Arrange
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
Data::MappedName anotherMappedName("ANOTHERTEST");
// Act
auto resultName = elementMap.setElementName(element, mappedName, 0);
auto resultName2 = elementMap.setElementName(element, anotherMappedName, 0, _sids, true);
auto mappedToElement = elementMap.find(element);
auto findAllResult = elementMap.findAll(element);
// Assert
EXPECT_EQ(resultName, mappedName);
EXPECT_EQ(resultName2, anotherMappedName);
EXPECT_EQ(mappedToElement, anotherMappedName);
EXPECT_EQ(findAllResult.size(), 1);
EXPECT_EQ(findAllResult[0].first, anotherMappedName);
}
TEST_F(ElementMapTest, setElementNameWithHashing)
{
// Arrange
Data::ElementMap elementMap;
std::ostringstream ss;
Data::IndexedName element("Edge", 1);
Data::MappedName elementNameHolder(element);// Will get modified by the encoder
const Data::MappedName expectedName(element);
// Act
elementMap.encodeElementName(
element.getType()[0], elementNameHolder, ss, nullptr, 0, nullptr, 0);
auto resultName = elementMap.setElementName(element, elementNameHolder, 0, _sids);
auto mappedToElement = elementMap.find(element);
// Assert
EXPECT_EQ(resultName, expectedName);
EXPECT_EQ(mappedToElement, expectedName);
}
TEST_F(ElementMapTest, eraseMappedName)
{
// Arrange
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
Data::MappedName anotherMappedName("ANOTHERTEST");
elementMap.setElementName(element, mappedName, 0);
elementMap.setElementName(element, anotherMappedName, 0);
// Act
auto sizeBefore = elementMap.size();
auto findAllBefore = elementMap.findAll(element);
elementMap.erase(anotherMappedName);
auto sizeAfter = elementMap.size();
auto findAllAfter = elementMap.findAll(element);
elementMap.erase(anotherMappedName);
auto sizeAfterRepeat = elementMap.size();
auto findAllAfterRepeat = elementMap.findAll(element);
// Assert
EXPECT_EQ(sizeBefore, 2);
EXPECT_EQ(findAllBefore.size(), 2);
EXPECT_EQ(findAllBefore[0].first, mappedName);
EXPECT_EQ(findAllBefore[1].first, anotherMappedName);
EXPECT_EQ(sizeAfter, 1);
EXPECT_EQ(findAllAfter.size(), 1);
EXPECT_EQ(findAllAfter[0].first, mappedName);
EXPECT_EQ(sizeAfterRepeat, 1);
EXPECT_EQ(findAllAfterRepeat.size(), 1);
EXPECT_EQ(findAllAfterRepeat[0].first, mappedName);
}
TEST_F(ElementMapTest, eraseIndexedName)
{
// Arrange
// Create two elements, edge1 and edge2, that have two mapped names each.
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
Data::MappedName anotherMappedName("ANOTHERTEST");
elementMap.setElementName(element, mappedName, 0);
elementMap.setElementName(element, anotherMappedName, 0);
Data::IndexedName element2("Edge", 2);
Data::MappedName mappedName2("TEST2");
Data::MappedName anotherMappedName2("ANOTHERTEST2");
elementMap.setElementName(element2, mappedName2, 0);
elementMap.setElementName(element2, anotherMappedName2, 0);
// Act
auto sizeBefore = elementMap.size();
auto findAllBefore = elementMap.findAll(element2);
elementMap.erase(element2);
auto sizeAfter = elementMap.size();
auto findAllAfter = elementMap.findAll(element2);
elementMap.erase(element2);
auto sizeAfterRepeat = elementMap.size();
auto findAllAfterRepeat = elementMap.findAll(element2);
// Assert
EXPECT_EQ(sizeBefore, 4);
EXPECT_EQ(findAllBefore.size(), 2);
EXPECT_EQ(findAllBefore[0].first, mappedName2);
EXPECT_EQ(findAllBefore[1].first, anotherMappedName2);
EXPECT_EQ(sizeAfter, 2);
EXPECT_EQ(findAllAfter.size(), 0);
EXPECT_EQ(sizeAfterRepeat, 2);
EXPECT_EQ(findAllAfterRepeat.size(), 0);
}
TEST_F(ElementMapTest, findMappedName)
{
// Arrange
// Create two elements, edge1 and edge2, that have two mapped names each.
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
Data::MappedName anotherMappedName("ANOTHERTEST");
elementMap.setElementName(element, mappedName, 0);
elementMap.setElementName(element, anotherMappedName, 0);
Data::IndexedName element2("Edge", 2);
Data::MappedName mappedName2("TEST2");
Data::MappedName anotherMappedName2("ANOTHERTEST2");
elementMap.setElementName(element2, mappedName2, 0);
elementMap.setElementName(element2, anotherMappedName2, 0);
// Act
auto findResult = elementMap.find(mappedName);
auto findResult2 = elementMap.find(mappedName2);
// Assert
EXPECT_EQ(findResult, element);
EXPECT_EQ(findResult2, element2);
}
TEST_F(ElementMapTest, findIndexedName)
{
// Arrange
// Create two elements, edge1 and edge2, that have two mapped names each.
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
Data::MappedName anotherMappedName("ANOTHERTEST");
elementMap.setElementName(element, mappedName, 0);
elementMap.setElementName(element, anotherMappedName, 0);
Data::IndexedName element2("Edge", 2);
Data::MappedName mappedName2("TEST2");
Data::MappedName anotherMappedName2("ANOTHERTEST2");
elementMap.setElementName(element2, mappedName2, 0);
elementMap.setElementName(element2, anotherMappedName2, 0);
// Act
// they return the first mapped name
auto findResult = elementMap.find(element);
auto findResult2 = elementMap.find(element2);
// Assert
EXPECT_EQ(findResult, mappedName);
EXPECT_EQ(findResult2, mappedName2);
}
TEST_F(ElementMapTest, findAll)
{
// Arrange
// Create two elements, edge1 and edge2, that have two mapped names each.
Data::ElementMap elementMap;
Data::IndexedName element("Edge", 1);
Data::MappedName mappedName("TEST");
Data::MappedName anotherMappedName("ANOTHERTEST");
elementMap.setElementName(element, mappedName, 0);
elementMap.setElementName(element, anotherMappedName, 0);
Data::IndexedName element2("Edge", 2);
Data::MappedName mappedName2("TEST2");
Data::MappedName anotherMappedName2("ANOTHERTEST2");
elementMap.setElementName(element2, mappedName2, 0);
elementMap.setElementName(element2, anotherMappedName2, 0);
// Act
// they return the first mapped name
auto findResult = elementMap.findAll(element);
auto findResult2 = elementMap.findAll(element2);
// Assert
EXPECT_EQ(findResult.size(), 2);
EXPECT_EQ(findResult[0].first, mappedName);
EXPECT_EQ(findResult[1].first, anotherMappedName);
EXPECT_EQ(findResult2.size(), 2);
EXPECT_EQ(findResult2[0].first, mappedName2);
EXPECT_EQ(findResult2[1].first, anotherMappedName2);
}
TEST_F(ElementMapTest, mimicOnePart)
{
// Arrange
// pattern: new doc, create Cube
// for a single part, there is no "naming algo" to speak of
std::ostringstream ss;
auto docName = "Unnamed";
LessComplexPart cube(1L, "Box", _hasher);
// Act
auto children = cube.elementMapPtr->getAll();
ss << docName << "#" << cube.name << "."
<< cube.elementMapPtr->find(Data::IndexedName("Face", 6));
// Assert
EXPECT_EQ(children.size(), 6);
EXPECT_EQ(children[0].index.toString(), "Face1");
EXPECT_EQ(children[0].name.toString(), "Face1");
EXPECT_EQ(children[1].index.toString(), "Face2");
EXPECT_EQ(children[1].name.toString(), "Face2");
EXPECT_EQ(children[2].index.toString(), "Face3");
EXPECT_EQ(children[2].name.toString(), "Face3");
EXPECT_EQ(children[3].index.toString(), "Face4");
EXPECT_EQ(children[3].name.toString(), "Face4");
EXPECT_EQ(children[4].index.toString(), "Face5");
EXPECT_EQ(children[4].name.toString(), "Face5");
EXPECT_EQ(children[5].index.toString(), "Face6");
EXPECT_EQ(children[5].name.toString(), "Face6");
EXPECT_EQ(ss.str(), "Unnamed#Box.Face6");
}
TEST_F(ElementMapTest, mimicSimpleUnion)
{
// Arrange
// pattern: new doc, create Cube, create Cylinder, Union of both (Cube first)
std::ostringstream ss;
std::ostringstream finalSs;
char* docName = "Unnamed";
LessComplexPart cube(1L, "Box", _hasher);
LessComplexPart cylinder(2L, "Cylinder", _hasher);
// Union (Fusion) operation via the Part Workbench
LessComplexPart unionPart(3L, "Fusion", _hasher);
// we are only going to simulate one face for testing purpose
Data::IndexedName uface3("Face", 3);
auto PartOp = "FUS";// Part::OpCodes::Fuse;
// Act
// act: simulate a union/fuse operation
auto parent = cube.elementMapPtr->getAll()[5];
Data::MappedName postfixHolder(std::string(Data::POSTFIX_MOD) + "2");
unionPart.elementMapPtr->encodeElementName(
postfixHolder[0], postfixHolder, ss, nullptr, unionPart.Tag, nullptr, unionPart.Tag);
auto postfixStr = postfixHolder.toString() + Data::ELEMENT_MAP_PREFIX + PartOp;
// act: with the fuse op, name against the cube's Face6
Data::MappedName uface3Holder(parent.index);
// we will invoke the encoder for face 3
unionPart.elementMapPtr->encodeElementName(
uface3Holder[0], uface3Holder, ss, nullptr, unionPart.Tag, postfixStr.c_str(), cube.Tag);
unionPart.elementMapPtr->setElementName(uface3, uface3Holder, unionPart.Tag, nullptr, true);
// act: generate a full toponame string for testing purposes
finalSs << docName << "#" << unionPart.name;
finalSs << ".";
finalSs << Data::ELEMENT_MAP_PREFIX + unionPart.elementMapPtr->find(uface3).toString();
finalSs << ".";
finalSs << uface3;
// Assert
EXPECT_EQ(postfixStr, ":M2;FUS");
EXPECT_EQ(unionPart.elementMapPtr->find(uface3).toString(), "Face6;:M2;FUS;:H1:8,F");
EXPECT_EQ(finalSs.str(), "Unnamed#Fusion.;Face6;:M2;FUS;:H1:8,F.Face3");
// explanation of "Fusion.;Face6;:M2;FUS;:H2:3,F.Face3" toponame
// Note: every postfix is prefixed by semicolon
// Note: the start/middle/end are separated by periods
//
// "Fusion" means that we are on the "Fusion" object.
// "." we are done with the first part
// ";Face6" means default inheritance comes from face 6 of the parent (which is a cube)
// ";:M2" means that a Workbench op has happened. "M" is the "Mod" directory in the source tree?
// ";FUS" means that a Fusion operation has happened. Notice the lack of a colon.
// ";:H2" means the subtending object (cylinder) has a tag of 2
// ":3" means the writing position is 3; literally how far into the current postfix we are
// ",F" means are of type "F" which is short for "Face" of Face3 of Fusion.
// "." we are done with the second part
// "Face3" is the localized name
}
TEST_F(ElementMapTest, mimicOperationAgainstSelf)
{
// Arrange
// pattern: new doc, create Cube, Mystery Op with self as target
std::ostringstream ss;
LessComplexPart finalPart(99L, "MysteryOp", _hasher);
// we are only going to simulate one face for testing purpose
Data::IndexedName uface3("Face", 3);
auto PartOp = "MYS";
auto ownFace6 = finalPart.elementMapPtr->getAll()[5];
Data::MappedName uface3Holder(ownFace6.index);
auto workbenchId = std::string(Data::POSTFIX_MOD) + "9999";
// Act
// act: with the mystery op, name against its own Face6 for some reason
Data::MappedName postfixHolder(workbenchId);
finalPart.elementMapPtr->encodeElementName(
postfixHolder[0], postfixHolder, ss, nullptr, finalPart.Tag, nullptr, finalPart.Tag);
auto postfixStr = postfixHolder.toString() + Data::ELEMENT_MAP_PREFIX + PartOp;
// we will invoke the encoder for face 3
finalPart.elementMapPtr->encodeElementName(uface3Holder[0],
uface3Holder,
ss,
nullptr,
finalPart.Tag,
postfixStr.c_str(),
finalPart.Tag);
// override not forced
finalPart.elementMapPtr->setElementName(uface3, uface3Holder, finalPart.Tag, nullptr, false);
// Assert
EXPECT_EQ(postfixStr, ":M9999;MYS");
EXPECT_EQ(finalPart.elementMapPtr->find(uface3).toString(), "Face3");// override not forced
EXPECT_EQ(uface3Holder.toString(), "Face6;:M9999;MYS;:H63:b,F");
// explaining ";Face6;:M2;MYS;:H2:3,F" name:
//
// ";Face6" means default inheritance comes from face 6 of the ownFace6 (which is itself)
// ";:M9999" means that a Workbench op happened. "M" is the "Mod" directory in the source tree?
// ";MYS" means that a "Mystery" operation has happened. Notice the lack of a colon.
// ";:H63" means the subtending object (cylinder) has a tag of 99 (63 in hex)
// ":b" means the writing position is b (hex); literally how far into the current postfix we are
// ",F" means are of type "F" which is short for "Face" of Face3 of Fusion.
}
TEST_F(ElementMapTest, hasChildElementMapTest)
{
// Arrange
Data::ElementMap::MappedChildElements child = {
Data::IndexedName("face", 1), 2, 7, 4L, Data::ElementMapPtr(), QByteArray(""), _sid};
std::vector<Data::ElementMap::MappedChildElements> children = {child};
LessComplexPart cubeFull(3L, "FullBox", _hasher);
cubeFull.elementMapPtr->addChildElements(cubeFull.Tag, children);
//
LessComplexPart cubeWithoutChildren(2L, "EmptyBox", _hasher);
// Act
bool resultFull = cubeFull.elementMapPtr->hasChildElementMap();
bool resultWhenEmpty = cubeWithoutChildren.elementMapPtr->hasChildElementMap();
// Assert
EXPECT_TRUE(resultFull);
EXPECT_FALSE(resultWhenEmpty);
}
TEST_F(ElementMapTest, hashChildMapsTest)
{
// Arrange
LessComplexPart cube(1L, "Box", _hasher);
auto childOneName = Data::IndexedName("Ping", 1);
Data::ElementMap::MappedChildElements childOne = {
childOneName,
2,
7,
3L,
Data::ElementMapPtr(),
QByteArray("abcdefghij"),// postfix must be 10 or more bytes to invoke hasher
_sid};
std::vector<Data::ElementMap::MappedChildElements> children = {childOne};
cube.elementMapPtr->addChildElements(cube.Tag, children);
auto before = _hasher->getIDMap();
// Act
cube.elementMapPtr->hashChildMaps(cube.Tag);
// Assert
auto after = _hasher->getIDMap();
EXPECT_EQ(before.size(), 0);
EXPECT_EQ(after.size(), 1);
}
TEST_F(ElementMapTest, addAndGetChildElementsTest)
{
// Arrange
LessComplexPart cube(1L, "Box", _hasher);
Data::ElementMap::MappedChildElements childOne = {
Data::IndexedName("Ping", 1),
2,
7,
3L,
Data::ElementMapPtr(),
QByteArray("abcdefghij"),// postfix must be 10 or more bytes to invoke hasher
_sid};
Data::ElementMap::MappedChildElements childTwo = {
Data::IndexedName("Pong", 2), 2, 7, 4L, Data::ElementMapPtr(), QByteArray("abc"), _sid};
std::vector<Data::ElementMap::MappedChildElements> children = {childOne, childTwo};
// Act
cube.elementMapPtr->addChildElements(cube.Tag, children);
auto result = cube.elementMapPtr->getChildElements();
// Assert
EXPECT_EQ(result.size(), 2);
EXPECT_TRUE(
std::any_of(result.begin(), result.end(), [](Data::ElementMap::MappedChildElements e) {
return e.indexedName.toString() == "Ping1";
}));
EXPECT_TRUE(
std::any_of(result.begin(), result.end(), [](Data::ElementMap::MappedChildElements e) {
return e.indexedName.toString() == "Pong2";
}));
}
// NOLINTEND(readability-magic-numbers)

View File

@@ -51,7 +51,7 @@ TEST(MappedName, namedConstructionWithMaxSize)
TEST(MappedName, namedConstructionDiscardPrefix)
{
// Arrange
std::string name = Data::ComplexGeoData::elementMapPrefix() + "TEST";
std::string name = std::string(Data::ELEMENT_MAP_PREFIX) + "TEST";
// Act
Data::MappedName mappedName(name.c_str());
@@ -80,7 +80,7 @@ TEST(MappedName, stringNamedConstruction)
TEST(MappedName, stringNamedConstructionDiscardPrefix)
{
// Arrange
std::string name = Data::ComplexGeoData::elementMapPrefix() + "TEST";
std::string name = std::string(Data::ELEMENT_MAP_PREFIX) + "TEST";
// Act
Data::MappedName mappedName(name);
@@ -560,7 +560,7 @@ TEST(MappedName, appendToBufferWithPrefix)
// Arrange
Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST");
std::string buffer("STUFF");
std::string elemMapPrefix = Data::ComplexGeoData::elementMapPrefix();
std::string elemMapPrefix = Data::ELEMENT_MAP_PREFIX;
// Act
mappedName.appendToBufferWithPrefix(buffer);
@@ -586,7 +586,7 @@ TEST(MappedName, toPrefixedString)
// Arrange
Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST");
std::string buffer("STUFF");
std::string elemMapPrefix = Data::ComplexGeoData::elementMapPrefix();
std::string elemMapPrefix = Data::ELEMENT_MAP_PREFIX;
// Act
buffer += mappedName.toPrefixedString();