Toposhape/Part:: fix, relocate and test element methods in ComplexGeoData and TopoShape

This commit is contained in:
bgbsww
2024-02-28 15:56:45 -05:00
parent 5e532494ca
commit 834bbff6b0
11 changed files with 273 additions and 105 deletions

View File

@@ -637,83 +637,6 @@ unsigned int ComplexGeoData::getMemSize() const
return 0;
}
void ComplexGeoData::traceElement(const MappedName &name, TraceCallback cb) const
{
long tag = this->Tag, encodedTag = 0;
int len = 0;
auto pos = findTagInElementName(name,&encodedTag,&len,nullptr,nullptr,true);
if(cb(name, len, encodedTag, tag) || pos < 0)
return;
if (name.startsWith(externalTagPostfix(), len))
return;
std::set<long> tagSet;
std::vector<MappedName> names;
if (tag)
tagSet.insert(std::abs(tag));
if (encodedTag)
tagSet.insert(std::abs(encodedTag));
names.push_back(name);
tag = encodedTag;
MappedName tmp;
bool first = true;
// TODO: element tracing without object is inheriently unsafe, because of
// possible external linking object which means the element may be encoded
// using external string table. Looking up the wrong table may accidentally
// cause circular mapping, and is actually quite easy to reproduce. See
//
// https://github.com/realthunder/FreeCAD_assembly3/issues/968
//
// A random depth limit is set here to not waste time. 'tagSet' above is
// also used for early detection of 'recursive' mapping.
for (int i=0; i<50; ++i) {
if(!len || len>pos)
return;
if(first) {
first = false;
size_t offset = 0;
if(name.startsWith(elementMapPrefix()))
offset = elementMapPrefix().size();
tmp = MappedName(name, offset, len);
}else
tmp = MappedName(tmp, 0, len);
tmp = dehashElementName(tmp);
names.push_back(tmp);
encodedTag = 0;
pos = findTagInElementName(tmp,&encodedTag,&len,nullptr,nullptr,true);
if (pos >= 0 && tmp.startsWith(externalTagPostfix(), len))
break;
if (encodedTag && tag != std::abs(encodedTag)
&& !tagSet.insert(std::abs(encodedTag)).second) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("circular element mapping");
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) {
auto doc = App::GetApplication().getActiveDocument();
if (doc) {
auto obj = doc->getObjectByID(this->Tag);
if (obj)
FC_LOG("\t" << obj->getFullName() << obj->getFullName() << "." << getIndexedName(name));
}
for (auto &name : names)
FC_ERR("\t" << name);
}
}
break;
}
if(cb(tmp, len, encodedTag, tag) || pos < 0)
return;
tag = encodedTag;
}
}
void ComplexGeoData::setMappedChildElements(const std::vector<Data::ElementMap::MappedChildElements> & children)
{
// DO NOT reset element map if there is one. Because we allow mixing child

View File

@@ -298,23 +298,10 @@ public:
virtual bool checkElementMapVersion(const char * ver) const;
/// 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;
static bool isElementName(const char* subName)
{
return (subName != nullptr) && (*subName != 0) && findElementName(subName) == subName;
}
/** 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.
*
* @sa traceElement()
*/
typedef std::function<bool(const MappedName&, int, long, long)> TraceCallback;
/** Iterate through the history of the give element name with a given callback
*
@@ -322,7 +309,10 @@ public:
* @param cb: trace callback with call signature.
* @sa TraceCallback
*/
void traceElement(const MappedName& name, TraceCallback cb) const;
void traceElement(const MappedName& name, TraceCallback cb) const
{
_elementMap->traceElement(name, Tag, cb);
}
/** Flush an internal buffering for element mapping */
virtual void flushElementMap() const;

View File

@@ -11,6 +11,8 @@
#include "App/Application.h"
#include "Base/Console.h"
#include "Document.h"
#include "DocumentObject.h"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
@@ -1348,5 +1350,94 @@ long ElementMap::getElementHistory(const MappedName& name, long masterTag, Mappe
}
}
void ElementMap::traceElement(const MappedName& name, long masterTag, TraceCallback cb) const
{
long encodedTag = 0;
int len = 0;
auto pos = name.findTagInElementName(&encodedTag, &len, nullptr, nullptr, true);
if (cb(name, len, encodedTag, masterTag) || pos < 0) {
return;
}
if (name.startsWith(POSTFIX_EXTERNAL_TAG, len)) {
return;
}
std::set<long> tagSet;
std::vector<MappedName> names;
if (masterTag) {
tagSet.insert(std::abs(masterTag));
}
if (encodedTag) {
tagSet.insert(std::abs(encodedTag));
}
names.push_back(name);
masterTag = encodedTag;
MappedName tmp;
bool first = true;
// TODO: element tracing without object is inheriently unsafe, because of
// possible external linking object which means the element may be encoded
// using external string table. Looking up the wrong table may accidentally
// cause circular mapping, and is actually quite easy to reproduce. See
//
// https://github.com/realthunder/FreeCAD_assembly3/issues/968
//
// A random depth limit is set here to not waste time. 'tagSet' above is
// also used for early detection of 'recursive' mapping.
for (int index = 0; index < 50; ++index) {
if (!len || len > pos) {
return;
}
if (first) {
first = false;
size_t offset = 0;
if (name.startsWith(ELEMENT_MAP_PREFIX)) {
offset = ELEMENT_MAP_PREFIX_SIZE;
}
tmp = MappedName(name, offset, len);
}
else {
tmp = MappedName(tmp, 0, len);
}
tmp = dehashElementName(tmp);
names.push_back(tmp);
encodedTag = 0;
pos = tmp.findTagInElementName(&encodedTag, &len, nullptr, nullptr, true);
if (pos >= 0 && tmp.startsWith(POSTFIX_EXTERNAL_TAG, len)) {
break;
}
if (encodedTag && masterTag != std::abs(encodedTag)
&& !tagSet.insert(std::abs(encodedTag)).second) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("circular element mapping");
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) {
auto doc = App::GetApplication().getActiveDocument();
if (doc) {
auto obj = doc->getObjectByID(masterTag);
if (obj) {
FC_LOG("\t" << obj->getFullName() << obj->getFullName() << "." << name);
}
}
for (auto& errname : names) {
FC_ERR("\t" << errname);
}
}
}
break;
}
if (cb(tmp, len, encodedTag, masterTag) || pos < 0) {
return;
}
masterTag = encodedTag;
}
}
}// Namespace Data

View File

@@ -45,6 +45,21 @@ namespace Data
class ElementMap;
using ElementMapPtr = std::shared_ptr<ElementMap>;
/** 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.
*
* @sa traceElement()
*/
typedef std::function<bool(const MappedName&, int, long, long)> TraceCallback;
/* 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.
@@ -185,6 +200,15 @@ public:
long masterTag,
MappedName *original=nullptr, std::vector<MappedName> *history=nullptr) const;
/** Iterate through the history of the give element name with a given 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;
private:
/** Serialize this map
* @param stream: serialized stream

View File

@@ -211,7 +211,7 @@ App::DocumentObjectExecReturn *MultiCommon::execute()
shapes.push_back(sh);
}
TopoShape res {};
TopoShape res {0};
res.makeElementBoolean(Part::OpCodes::Common, shapes);
if (res.isNull()) {
throw Base::RuntimeError("Resulting shape is null");

View File

@@ -1240,6 +1240,14 @@ public:
void copyElementMap(const TopoShape & topoShape, const char *op=nullptr);
bool canMapElement(const TopoShape &other) const;
void cacheRelatedElements(const Data::MappedName & name,
HistoryTraceType sameType,
const QVector<Data::MappedElement> & names) const;
bool getRelatedElementsCached(const Data::MappedName & name,
HistoryTraceType sameType,
QVector<Data::MappedElement> &names) const;
void mapSubElement(const TopoShape &other,const char *op=nullptr, bool forceHasher=false);
void mapSubElement(const std::vector<TopoShape> &shapes, const char *op=nullptr);
void mapSubElementsTo(std::vector<TopoShape>& shapes, const char* op = nullptr) const;

View File

@@ -5002,23 +5002,22 @@ bool TopoShape::isSame(const Data::ComplexGeoData &_other) const
&& Hasher == other.Hasher
&& _Shape.IsEqual(other._Shape);
}
void TopoShape::cacheRelatedElements(const Data::MappedName &name,
bool sameType,
HistoryTraceType sameType,
const QVector<Data::MappedElement> & names) const
{
INIT_SHAPE_CACHE();
_Cache->insertRelation(ShapeRelationKey(name,sameType), names);
initCache();
_cache->insertRelation(ShapeRelationKey(name,sameType), names);
}
bool TopoShape::getRelatedElementsCached(const Data::MappedName &name,
bool sameType,
HistoryTraceType sameType,
QVector<Data::MappedElement> &names) const
{
if(!_Cache)
if(!_cache)
return false;
auto it = _Cache->relations.find(ShapeRelationKey(name,sameType));
if(it == _Cache->relations.end())
auto it = _cache->relations.find(ShapeRelationKey(name,sameType));
if(it == _cache->relations.end())
return false;
names = it->second;
return true;