From e5beb5ab2bcddc51da8e2d2376d429d259c3777f Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 15 Apr 2021 20:37:25 +0800 Subject: [PATCH] App: add APIs in ComplexGeoData for element name mapping These are the fundation for storing and querying the new topological naming of geometry element. To get an overview of this fundation, please check out the following article https://github.com/realthunder/FreeCAD_assembly3/wiki/Topological-Naming There are some changes in topo naming internal storage, which changes from plain string to two dedicated class IndexedName (for indexed geometry element name) and MappedName. These two classes are created to improve topological naming performance in terms of speed, runtime memory, and persistence storage size. --- src/App/CMakeLists.txt | 2 + src/App/ComplexGeoData.cpp | 2112 ++++++++++++++++++++++++++++++- src/App/ComplexGeoData.h | 260 +++- src/App/ComplexGeoDataPy.xml | 61 + src/App/ComplexGeoDataPyImp.cpp | 190 ++- src/App/DocumentObject.cpp | 14 + src/App/DocumentObject.h | 14 + src/App/DocumentObserver.cpp | 60 +- src/App/DocumentObserver.h | 6 +- src/App/GeoFeature.cpp | 137 +- src/App/GeoFeature.h | 58 +- src/App/MappedElement.cpp | 258 ++++ src/App/MappedElement.h | 847 +++++++++++++ src/App/Property.cpp | 7 + src/App/Property.h | 2 + src/App/PropertyGeo.cpp | 62 +- src/App/PropertyGeo.h | 12 + src/App/PropertyLinks.cpp | 449 +++++-- src/App/PropertyLinks.h | 23 +- src/App/StringHasher.cpp | 94 ++ src/App/StringHasher.h | 8 + src/App/StringIDPy.xml | 6 + src/App/StringIDPyImp.cpp | 7 + src/Base/Tools.h | 25 + 24 files changed, 4548 insertions(+), 166 deletions(-) create mode 100644 src/App/MappedElement.cpp create mode 100755 src/App/MappedElement.h diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index 8691b6a21b..a1d127daeb 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -273,6 +273,7 @@ SET(FreeCADApp_CPP_SRCS AutoTransaction.cpp Branding.cpp ColorModel.cpp + MappedElement.cpp ComplexGeoData.cpp ComplexGeoDataPyImp.cpp Enumeration.cpp @@ -289,6 +290,7 @@ SET(FreeCADApp_HPP_SRCS AutoTransaction.h Branding.h ColorModel.h + MappedElement.h ComplexGeoData.h Enumeration.h Material.h diff --git a/src/App/ComplexGeoData.cpp b/src/App/ComplexGeoData.cpp index 4b8de52b36..e84854af95 100644 --- a/src/App/ComplexGeoData.cpp +++ b/src/App/ComplexGeoData.cpp @@ -28,16 +28,1179 @@ #endif #include -#include +#include +#include +#include +#include +#include #include "ComplexGeoData.h" #include +#include +#include #include +#include #include +#include +#include "Application.h" +#include "Document.h" +#include "DocumentObject.h" +#include "MappedElement.h" +FC_LOG_LEVEL_INIT("ComplexGeoData", true,true) +namespace bio = boost::iostreams; using namespace Data; +#ifdef _FC_MEM_TRACE + +static int64_t _MemSize; +static int64_t _MemMaxSize; + +struct MemUnit { + int count; + int maxcount; +}; +static std::map _MemUnits; + +template +struct MemoryMapAllocator : std::allocator { + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + template struct rebind { typedef MemoryMapAllocator other; }; + + MemoryMapAllocator() {} + + template + MemoryMapAllocator(const MemoryMapAllocator& u) : std::allocator(u) {} + + pointer allocate(size_type size, std::allocator::const_pointer = 0) { + void* p = std::malloc(size * sizeof(T)); + if(p == 0) + throw std::bad_alloc(); + _MemSize += size * sizeof(T); + if (_MemSize > _MemMaxSize) + _MemMaxSize = _MemSize; + auto &unit = _MemUnits[sizeof(T)]; + if (++unit.count > unit.maxcount) + unit.maxcount = unit.count; + return static_cast(p); + } + void deallocate(pointer p, size_type size) { + _MemSize -= size * sizeof(T); + --_MemUnits[sizeof(T)].count; + std::free(p); + } +}; + +#endif + +namespace Data { + +struct MappedNameRef +{ + MappedName name; + ElementIDRefs sids; + std::unique_ptr next; + + MappedNameRef() {} + + 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 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 tmp; + tmp.swap(this->next); + this->next = std::move(tmp->next); + } + return true; + } + + for (std::unique_ptr *p = &this->next; *p; p = &(*p)->next) { + if ((*p)->name == name) { + std::unique_ptr tmp; + tmp.swap(*p); + *p = std::move(tmp->next); + return true; + } + } + return false; + } + + void clear() + { + this->name.clear(); + this->sids.clear(); + this->next.reset(); + } +}; + +struct IndexedElements +{ + std::deque names; + std::map children; +}; + +struct ChildMapInfo +{ + int index = 0; + MappedChildElements * childMap = nullptr; + std::map mapIndices; +}; + +struct CStringComp +{ + bool operator()(const char *a, const char *b) const + { + return std::strcmp(a, b) < 0; + } +}; + +inline std::ostream & operator << (std::ostream &s, const QByteArray &bytes) +{ + s.write(bytes.constData(), bytes.size()); + return s; +} + +// Because the existence of hierarchical element maps, for the same document +// we may store an element map more than once in multiple objects. And because +// we may want to support partial loading, we choose to tolerate such redundancy +// for now. +// +// In order to not waste memory space when the file is loaded, we use the +// following two maps to assign a one-time id for each unique element map. The +// id will be saved together with the element map. +// +// When restoring, we'll read back the id and lookup for an existing element map +// with the same id, and skip loading the current map if one is found. +// +// TODO: Note that the same redundancy can be found when saving OCC shapes, +// because we currently save shapes for each object separately. After restoring, +// any shape sharing is lost. But again, we do want to keep separate shape files +// because of partial loading. The same technique used here can be applied to +// restore shape sharing. +static std::unordered_map _ElementMapToId; +static std::unordered_map _IdToElementMap; + +class ElementMap : public std::enable_shared_from_this { +public: + + ElementMap() + { + static bool inited; + if (!inited) { + inited = true; + App::GetApplication().signalStartSaveDocument.connect( + [](const App::Document &, const std::string &) { + _ElementMapToId.clear(); + }); + App::GetApplication().signalFinishSaveDocument.connect( + [](const App::Document &, const std::string &) { + _ElementMapToId.clear(); + }); + App::GetApplication().signalStartRestoreDocument.connect( + [](const App::Document &) { + _IdToElementMap.clear(); + }); + App::GetApplication().signalFinishRestoreDocument.connect( + [](const App::Document &) { + _IdToElementMap.clear(); + }); + } + } + + void beforeSave(const App::StringHasherRef & hasher) const { + unsigned & id = _ElementMapToId[this]; + if (!id) + id = _ElementMapToId.size(); + this->_id = id; + + for (auto & v : this->indexedNames) { + for (const MappedNameRef & ref : v.second.names) { + for (const MappedNameRef *r=&ref; r; r=r->next.get()) { + for (const App::StringIDRef & sid : r->sids) { + if (sid.isFromSameHasher(hasher)) + sid.mark(); + } + } + } + for (auto & vv : v.second.children) { + if (vv.second.elementMap) + vv.second.elementMap->beforeSave(hasher); + for (auto & sid : vv.second.sids) { + if (sid.isFromSameHasher(hasher)) + sid.mark(); + } + } + } + } + + const MappedNameRef * findMappedRef(const IndexedName & idx) const + { + auto iter = this->indexedNames.find(idx.getType()); + if (iter == this->indexedNames.end()) + return nullptr; + auto & indices = iter->second; + if (idx.getIndex() >= (int)indices.names.size()) + return nullptr; + return &indices.names[idx.getIndex()]; + } + + MappedNameRef * findMappedRef(const IndexedName & idx) + { + auto iter = this->indexedNames.find(idx.getType()); + if (iter == this->indexedNames.end()) + return nullptr; + auto & indices = iter->second; + if (idx.getIndex() >= (int)indices.names.size()) + return nullptr; + return &indices.names[idx.getIndex()]; + } + + MappedNameRef & mappedRef(const IndexedName & idx) + { + assert(idx); + auto & indices = this->indexedNames[idx.getType()]; + if (idx.getIndex() >= (int)indices.names.size()) + indices.names.resize(idx.getIndex()+1); + return indices.names[idx.getIndex()]; + } + + static void addPostfix(const QByteArray & postfix, + std::map &postfixMap, + std::vector &postfixes) + { + if (postfix.isEmpty()) + return; + auto res = postfixMap.insert(std::make_pair(postfix, 0)); + if (res.second) { + postfixes.push_back(postfix); + res.first->second = (int)postfixes.size(); + } + } + + void collectChildMaps(std::map &childMapSet, + std::vector &childMaps, + std::map &postfixMap, + std::vector &postfixes) const + { + auto res = childMapSet.insert(std::make_pair(this, 0)); + if (!res.second) + return; + + for (auto & v : this->indexedNames) { + addPostfix(QByteArray::fromRawData(v.first, qstrlen(v.first)), postfixMap, postfixes); + + for (auto & vv : v.second.children) { + auto & child = vv.second; + if (child.elementMap) + child.elementMap->collectChildMaps(childMapSet, childMaps, postfixMap, postfixes); + } + } + + for (auto & v : this->mappedNames) + addPostfix(v.first.constPostfix(), postfixMap, postfixes); + + childMaps.push_back(this); + res.first->second = (int)childMaps.size(); + } + + void save(std::ostream &s, + int index, + const std::map &childMapSet, + const std::map &postfixMap) const + { + s << "\nElementMap " << index << ' ' << this->_id << ' ' + << this->indexedNames.size() << '\n'; + + for (auto & v : this->indexedNames) { + s << '\n' << v.first << '\n'; + + s << "\nChildCount " << v.second.children.size() << '\n'; + for (auto & vv : v.second.children) { + auto & child = vv.second; + int mapIndex = 0; + if (child.elementMap) { + auto it = childMapSet.find(child.elementMap.get()); + if (it == childMapSet.end() || it->second == 0) + FC_ERR("Invalid child element map"); + else + mapIndex = it->second; + } + s << child.indexedName.getIndex() << ' ' + << child.offset << ' ' + << child.count << ' ' + << child.tag << ' ' + << mapIndex << ' ' + << child.postfix << ' ' + << '0'; + for (auto & sid : child.sids) { + if (sid.isMarked()) + s << '.' << sid.value(); + } + s << '\n'; + } + + s << "\nNameCount " << v.second.names.size() << '\n'; + if (v.second.names.empty()) + continue; + + boost::io::ios_flags_saver ifs(s); + s << std::hex; + + for (auto & ref : v.second.names) { + for (auto r = &ref; r; r=r->next.get()) { + if (!r->name) + break; + + App::StringID::IndexID prefixid; + prefixid.id = 0; + IndexedName idx(r->name.dataBytes()); + bool printName = true; + if (idx) { + auto key = QByteArray::fromRawData(idx.getType(), qstrlen(idx.getType())); + auto it = postfixMap.find(key); + if (it != postfixMap.end()) { + s << ':' << it->second << '.' << idx.getIndex(); + printName = false; + } + } else { + prefixid = App::StringID::fromString(r->name.dataBytes()); + if (prefixid.id) { + for (auto & sid : r->sids) { + if (sid.isMarked() && sid.value() == prefixid.id) { + s << '$' << r->name.dataBytes(); + printName = false; + break; + } + } + if (printName) + prefixid.id = 0; + } + } + if (printName) + s << ';' << r->name.dataBytes(); + + const QByteArray & postfix = r->name.postfixBytes(); + if (postfix.isEmpty()) + s << ".0"; + else { + auto it = postfixMap.find(postfix); + assert(it != postfixMap.end()); + s << '.' << it->second; + } + for (auto & sid : r->sids) { + if (sid.isMarked() && sid.value() != prefixid.id) + s << '.' << sid.value(); + } + + s << ' '; + } + s << "0\n"; + } + } + s << "\nEndMap\n"; + } + + void save(std::ostream &s) const { + std::map childMapSet; + std::vector childMaps; + std::map postfixMap; + std::vector postfixes; + + collectChildMaps(childMapSet, childMaps, postfixMap, postfixes); + + s << this->_id << " PostfixCount " << postfixes.size() << '\n'; + for (auto & p : postfixes) + s << p << '\n'; + int index = 0; + s << "\nMapCount " << childMaps.size() << '\n'; + for (auto & elementMap : childMaps) + elementMap->save(s, ++index, childMapSet, postfixMap); + } + + ElementMapPtr restore(App::StringHasherRef hasher, std::istream &s) + { + const char * msg = "Invalid element map"; + + unsigned id; + int count = 0; + std::string tmp; + if (! (s >> id >> tmp >> count) || tmp != "PostfixCount") + FC_THROWM(Base::RuntimeError, msg); + + auto & map = _IdToElementMap[id]; + if (map) + return map; + + std::vector postfixes; + postfixes.reserve(count); + for (int i=0; i < count; ++i) { + postfixes.emplace_back(); + s >> postfixes.back(); + } + + std::vector childMaps; + count = 0; + if (! (s >> tmp >> count) || tmp != "MapCount" || count==0) + FC_THROWM(Base::RuntimeError, msg); + childMaps.reserve(count-1); + for (int i=0; i()->restore( + hasher, s, childMaps, postfixes)); + } + + return restore(hasher, s, childMaps, postfixes); + } + + ElementMapPtr restore(App::StringHasherRef hasher, + std::istream &s, + std::vector &childMaps, + const std::vector &postfixes) + { + const char * msg = "Invalid element map"; + std::string tmp; + int index = 0; + int typeCount = 0; + unsigned id = 0; + if (! (s >> tmp >> index >> id >> typeCount) || tmp != "ElementMap") + FC_THROWM(Base::RuntimeError, msg); + + auto & map = _IdToElementMap[id]; + if (map) { + do { + if (! std::getline(s, tmp)) + FC_THROWM(Base::RuntimeError, "unexpected end of child element map"); + } while(tmp != "EndMap"); + return map; + } + map = shared_from_this(); + + const char *hasherWarn = nullptr; + const char *hasherIDWarn = nullptr; + const char *postfixWarn = nullptr; + const char *childSIDWarn = nullptr; + std::vector tokens; + + for (int i=0; i> tmp)) + FC_THROWM(Base::RuntimeError, "missing element type"); + IndexedName idx(tmp.c_str(), 1); + + if (! (s >> tmp >> count) || tmp != "ChildCount") + FC_THROWM(Base::RuntimeError, "missing element child count"); + + auto & indices = this->indexedNames[idx.getType()]; + for (int j=0; j> cindex >> offset >> count >> tag >> mapIndex >> tmp)) + FC_THROWM(Base::RuntimeError, "Invalid element child"); + if (cindex < 0) + FC_THROWM(Base::RuntimeError, "Invalid element child index"); + if (offset < 0) + FC_THROWM(Base::RuntimeError, "Invalid element child offset"); + if (mapIndex >= index || mapIndex < 0 || mapIndex > (int)childMaps.size()) + FC_THROWM(Base::RuntimeError, "Invalid element child map index"); + auto & child = indices.children[cindex+offset+count]; + child.indexedName = IndexedName::fromConst(idx.getType(), cindex); + child.offset = offset; + child.count = count; + child.tag = tag; + if (mapIndex > 0) + child.elementMap = childMaps[mapIndex-1]; + else + child.elementMap = nullptr; + child.postfix = tmp.c_str(); + this->childElements[child.postfix].childMap = &child; + this->childElementSize += child.count; + + if (! (s >> tmp)) + FC_THROWM(Base::RuntimeError, "Invalid element child string id"); + + tokens.clear(); + boost::split(tokens, tmp, boost::is_any_of(".")); + if (tokens.size() > 1) { + child.sids.reserve(tokens.size()-1); + for (unsigned k=1; kgetID(n); + if (!sid) + childSIDWarn = "Missing element child string id"; + else + child.sids.push_back(sid); + } + } + } + + if (! (s >> tmp >> count) || tmp != "NameCount") + FC_THROWM(Base::RuntimeError, "missing element name count"); + + boost::io::ios_flags_saver ifs(s); + s >> std::hex; + + indices.names.resize(count); + for (int j=0; j> tmp)) + FC_THROWM(Base::RuntimeError, "Failed to read element name"); + if (tmp == "0") + break; + if (k++ != 0) { + ref->next.reset(new MappedNameRef); + ref = ref->next.get(); + } + tokens.clear(); + boost::split(tokens, tmp, boost::is_any_of(".")); + if (tokens.size() < 2) + FC_THROWM(Base::RuntimeError, "Invalid element entry"); + + int offset = 1; + App::StringID::IndexID prefixid; + prefixid.id = 0; + + switch(tokens[0][0]) { + case ':': { + if (tokens.size() < 3) + FC_THROWM(Base::RuntimeError, "Invalid element entry"); + ++offset; + long n = strtol(tokens[0].c_str()+1, nullptr, 16); + if (n <= 0 || n > (int)postfixes.size()) + FC_THROWM(Base::RuntimeError, "Invalid element name index"); + long m = strtol(tokens[1].c_str(), nullptr, 16); + ref->name = MappedName(IndexedName::fromConst(postfixes[n-1].c_str(), m)); + break; + } + case '$': + ref->name = MappedName(tokens[0].c_str()+1); + prefixid = App::StringID::fromString(ref->name.dataBytes()); + break; + case ';': + ref->name = MappedName(tokens[0].c_str()+1); + break; + default: + FC_THROWM(Base::RuntimeError, "Invalid element name marker"); + } + + if (tokens[offset] != "0") { + long n = strtol(tokens[offset].c_str(), nullptr, 16); + if (n <= 0 || n > (int)postfixes.size()) + postfixWarn = "Invalid element postfix index"; + else + ref->name += postfixes[n-1]; + } + + this->mappedNames.emplace(ref->name, idx); + + if (!hasher) { + if (offset + 1 < (int)tokens.size()) + hasherWarn = "No hasher"; + continue; + } + + ref->sids.reserve(tokens.size()-offset-1 + prefixid.id?1:0); + if (prefixid.id) { + auto sid = hasher->getID(prefixid.id); + if (!sid) + hasherIDWarn = "Missing element name prefix id"; + else + ref->sids.push_back(sid); + } + for (int l=offset+1; l<(int)tokens.size(); ++l) { + long id = strtol(tokens[l].c_str(), nullptr, 16); + auto sid = hasher->getID(id); + if (!sid) + hasherIDWarn = "Invalid element name string id"; + else + ref->sids.push_back(sid); + } + } + } + } + if (hasherWarn) + FC_WARN(hasherWarn); + if (hasherIDWarn) + FC_WARN(hasherIDWarn); + if (postfixWarn) + FC_WARN(postfixWarn); + if (childSIDWarn) + FC_WARN(childSIDWarn); + + if (! (s >> tmp) || tmp != "EndMap") + FC_THROWM(Base::RuntimeError, "unexpected end of child element map"); + + return shared_from_this(); + } + + MappedName addName(MappedName & name, + const IndexedName & idx, + const ElementIDRefs &sids, + bool overwrite, + IndexedName * existing) + { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + if (name.find("#") >= 0 + && ComplexGeoData::findTagInElementName(name) < 0) + { + FC_ERR("missing tag postfix " << name); + } + } + do { + if (overwrite) + erase(idx); + auto ret = mappedNames.insert(std::make_pair(name, idx)); + if (ret.second) { + ret.first->first.compact(); + mappedRef(idx).append(ret.first->first, sids); + FC_TRACE(idx << " -> " << name); + return ret.first->first; + } + if(ret.first->second == idx) { + FC_TRACE("duplicate " << idx << " -> " << name); + return ret.first->first; + } + if(!overwrite) { + if (existing) + *existing = ret.first->second; + return MappedName(); + } + + erase(ret.first->first); + } while (true); + } + + bool erase(const MappedName &name) + { + auto it = this->mappedNames.find(name); + if (it == this->mappedNames.end()) + return false; + MappedNameRef * ref = findMappedRef(it->second); + if (!ref) + return false; + ref->erase(name); + this->mappedNames.erase(it); + return true; + } + + bool erase(const IndexedName &idx) + { + auto iter = this->indexedNames.find(idx.getType()); + if (iter == this->indexedNames.end()) + return false; + auto & indices = iter->second; + if (idx.getIndex() >= (int)indices.names.size()) + return false; + auto & ref = indices.names[idx.getIndex()]; + for (auto *r = &ref; r; r = r->next.get()) + this->mappedNames.erase(r->name); + ref.clear(); + return true; + } + + IndexedName find(const MappedName &name, ElementIDRefs * sids = nullptr) const + { + auto it = mappedNames.find(name); + if (it == mappedNames.end()) { + if (childElements.isEmpty()) + return IndexedName(); + + int len = 0; + if (ComplexGeoData::findTagInElementName( + name,nullptr,&len,nullptr,nullptr,false,false) < 0) + return IndexedName(); + QByteArray key = name.toRawBytes(len); + auto it = this->childElements.find(key); + if (it == this->childElements.end()) + return IndexedName(); + + const auto & child = *it.value().childMap; + IndexedName res; + + MappedName childName = MappedName::fromRawData(name, 0, len); + if (child.elementMap) + res = child.elementMap->find(childName, sids); + else + res = childName.toIndexedName(); + + if (res && boost::equals(res.getType(), child.indexedName.getType()) + && child.indexedName.getIndex() <= res.getIndex() + && child.indexedName.getIndex() + child.count > res.getIndex()) + { + res.setIndex(res.getIndex() + it.value().childMap->offset); + return res; + } + + return IndexedName(); + } + + if (sids) { + const MappedNameRef * ref = findMappedRef(it->second); + for (; ref; ref = ref->next.get()) { + if (ref->name == name) { + if (!sids->size()) + *sids = ref->sids; + else + *sids += ref->sids; + break; + } + } + } + return it->second; + } + + MappedName find(const IndexedName &idx, ElementIDRefs * sids = nullptr) const + { + if (!idx) + return MappedName(); + + auto iter = this->indexedNames.find(idx.getType()); + if (iter == this->indexedNames.end()) + return MappedName(); + + auto & indices = iter->second; + if (idx.getIndex() < (int)indices.names.size()) { + const MappedNameRef & ref = indices.names[idx.getIndex()]; + if (ref.name) { + if (sids) { + if (!sids->size()) + *sids = ref.sids; + else + *sids += ref.sids; + } + return ref.name; + } + } + + auto it = indices.children.upper_bound(idx.getIndex()); + if (it != indices.children.end() + && it->second.indexedName.getIndex()+it->second.offset <= idx.getIndex()) + { + auto & child = it->second; + MappedName name; + IndexedName childIdx(idx.getType(), idx.getIndex() - child.offset); + if (child.elementMap) + name = child.elementMap->find(childIdx, sids); + else + name = MappedName(childIdx); + if (name) { + name += child.postfix; + return name; + } + } + return MappedName(); + } + + std::vector > + findAll(const IndexedName &idx) const + { + std::vector > res; + if (!idx) + return res; + + auto iter = this->indexedNames.find(idx.getType()); + if (iter == this->indexedNames.end()) + return res; + + auto & indices = iter->second; + if (idx.getIndex() < (int)indices.names.size()) { + const MappedNameRef & ref = indices.names[idx.getIndex()]; + int count = 0; + for (auto r = &ref; r; r = r->next.get()) { + if (r->name) + ++count; + } + if (count) { + res.reserve(count); + for (auto r = &ref; r; r = r->next.get()) { + if (r->name) + res.emplace_back(r->name, r->sids); + } + return res; + } + } + + auto it = indices.children.upper_bound(idx.getIndex()); + if (it != indices.children.end() + && it->second.indexedName.getIndex()+it->second.offset <= idx.getIndex()) + { + auto & child = it->second; + IndexedName childIdx(idx.getType(), idx.getIndex() - child.offset); + if (child.elementMap) { + res = child.elementMap->findAll(childIdx); + for (auto &v : res) + v.first += child.postfix; + } else + res.emplace_back(MappedName(childIdx) + child.postfix, ElementIDRefs()); + } + + return res; + } + + // 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 + findAllStartsWith(const char *prefix) const + { + std::vector res; + MappedName mapped(prefix); + for(auto it=mappedNames.lower_bound(mapped);it!=mappedNames.end();++it) { + if(it->first.startsWith(prefix)) + res.emplace_back(it->first, it->second); + } + return res; + } +#endif + + unsigned long size() const + { + return mappedNames.size() + childElementSize; + } + + bool empty() const + { + return mappedNames.empty() && childElementSize == 0; + } + + bool hasChildElementMap() const + { + return !childElements.empty(); + } + + void hashChildMaps(ComplexGeoData & master) + { + if (childElements.empty() || !master.Hasher) + return; + std::ostringstream ss; + for (auto & v : this->indexedNames) { + for (auto & vv : v.second.children) { + auto & child = vv.second; + int len = 0; + long tag; + int pos = ComplexGeoData::findTagInElementName( + MappedName::fromRawData(child.postfix), + &tag, &len, nullptr, nullptr, false, false); + if (pos > 10) { + MappedName postfix = master.hashElementName( + MappedName::fromRawData(child.postfix.constData(), pos), child.sids); + ss.str(""); + ss << MappedChildElements::prefix() << postfix; + MappedName tmp; + master.encodeElementName(child.indexedName[0], + tmp, ss, nullptr, nullptr, child.tag, true); + this->childElements.remove(child.postfix); + child.postfix = tmp.toBytes(); + this->childElements[child.postfix].childMap = & child; + } + } + } + } + + void addChildElements(ComplexGeoData & master, + const std::vector &children) + { + std::ostringstream ss; + ss << std::hex; + + // To avoid possibly very long recursive child map lookup, resulting very + // long mapped names, we try to resolve the grand child map now. + std::vector expansion; + for (auto it=children.begin(); it!=children.end(); ++it) { + auto & child = *it; + if (!child.elementMap || child.elementMap->childElements.empty()) { + if (expansion.size()) + expansion.push_back(child); + continue; + } + auto & indices = child.elementMap->indexedNames[child.indexedName.getType()]; + if (indices.children.empty()) { + if (expansion.size()) + expansion.push_back(child); + continue; + } + + // Note that it is allow to have both mapped names and child map. We + // may have to split the current child mapping into pieces. + + int start = child.indexedName.getIndex(); + int end = start + child.count; + for (auto iter=indices.children.upper_bound(start); iter!=indices.children.end(); ++iter) { + auto & grandchild = iter->second; + int istart = grandchild.indexedName.getIndex() + grandchild.offset; + int iend = istart + grandchild.count; + if (end <= istart) + break; + if (istart >= end) { + if (expansion.size()) { + expansion.push_back(child); + expansion.back().indexedName.setIndex(start); + expansion.back().count = end - start; + } + break; + } + if (expansion.empty()) { + expansion.reserve(children.size() + 10); + expansion.insert(expansion.end(), children.begin(), it); + } + expansion.push_back(child); + auto * entry = & expansion.back(); + if (istart > start) { + entry->indexedName.setIndex(start); + entry->count = istart - start; + + expansion.push_back(child); + entry = & expansion.back(); + } else + istart = start; + + if (iend > end) + iend = end; + + entry->indexedName.setIndex(istart - grandchild.offset); + entry->count = iend - istart; + entry->offset += grandchild.offset; + entry->elementMap = grandchild.elementMap; + entry->sids += grandchild.sids; + if (grandchild.postfix.size()) { + if (entry->postfix.size() + && !entry->postfix.startsWith(ComplexGeoData::elementMapPrefix().c_str())) + { + entry->postfix = grandchild.postfix + + ComplexGeoData::elementMapPrefix().c_str() + entry->postfix; + } else + entry->postfix = grandchild.postfix + entry->postfix; + } + + start = iend; + if (start >= end) + break; + } + if (expansion.size() && start < end) { + expansion.push_back(child); + expansion.back().indexedName.setIndex(start); + expansion.back().count = end - start; + } + } + + for (auto & child : expansion.size()?expansion:children) { + if (!child.indexedName || !child.count) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_ERR("invalid mapped child element"); + continue; + } + + ss.str(""); + MappedName tmp; + + ChildMapInfo *entry = nullptr; + + // do child mapping only if the child element count >= 5 + if (child.count >= 5 || !child.elementMap) { + master.encodeElementName(child.indexedName[0], + tmp, ss, nullptr, child.postfix.constData(), child.tag, true); + + // Perform some disambiguation in case the same shape is mapped + // multiple times, e.g. draft array. + entry = & childElements[tmp.toBytes()]; + int mapIndex = entry->mapIndices[child.elementMap.get()]++; + ++entry->index; + if (entry->index != 1 && child.elementMap && mapIndex == 0) { + // This child has duplicated 'tag' and 'postfix', but it + // has its own element map. We'll expand this map now. + entry = nullptr; + } + } + + if (!entry) { + IndexedName childIdx(child.indexedName); + IndexedName idx(childIdx.getType(), childIdx.getIndex()+child.offset); + for (int i=0; ifind(childIdx, &sids); + if (!name) { + if (!child.tag || child.tag == master.Tag) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("unmapped element"); + continue; + } + name = MappedName(childIdx); + } + ss.str(""); + master.encodeElementName(idx[0], name, ss, &sids, + child.postfix.constData(), child.tag); + master.setElementName(idx, name, &sids); + } + continue; + } + + if (entry->index != 1) { + // There is some ambiguity in child mapping. We need some + // additional postfix for disambiguation. NOTE: We are not + // using ComplexGeoData::indexPostfix() so as to not confuse + // other code that actually uses this postfix for indexing + // purposes. Here, we just need some postfix for + // disambiguation. We don't need to extract the index. + ss.str(""); + ss << ComplexGeoData::elementMapPrefix() << ":C" << entry->index-1; + + tmp.clear(); + master.encodeElementName(child.indexedName[0], + tmp, ss, nullptr, child.postfix.constData(), child.tag, true); + + entry = & childElements[tmp.toBytes()]; + if (entry->childMap) { + FC_ERR("duplicate mapped child element"); + continue; + } + } + + auto & indices = this->indexedNames[child.indexedName.getType()]; + auto res = indices.children.emplace( + child.indexedName.getIndex() + child.offset + child.count, child); + if (!res.second) { + if (!entry->childMap) + this->childElements.remove(tmp.toBytes()); + FC_ERR("duplicate mapped child element"); + continue; + } + + auto & insertedChild = res.first->second; + insertedChild.postfix = tmp.toBytes(); + entry->childMap = & insertedChild; + childElementSize += insertedChild.count; + } + } + + std::vector getChildElements() const + { + std::vector res; + res.reserve(this->childElements.size()); + for (auto & v : this->childElements) + res.push_back(*v.childMap); + return res; + } + + std::vector getAll() const { + std::vector ret; + ret.reserve(size()); + for (auto &v : this->mappedNames) + ret.emplace_back(v.first, v.second); + for (auto &v : this->childElements) { + auto & child = *v.childMap; + IndexedName idx(child.indexedName); + idx.setIndex(idx.getIndex() + child.offset); + IndexedName childIdx(child.indexedName); + for (int i=0; ifind(childIdx); + else + name = MappedName(childIdx); + if (name) { + name += child.postfix; + ret.emplace_back(name, idx); + } + } + } + return ret; + } + +private: + std::map indexedNames; + + std::map +#ifdef _FC_MEM_TRACE + ,MemoryMapAllocator > +#endif + > mappedNames; + + QHash childElements; + + std::size_t childElementSize = 0; + + mutable unsigned _id = 0; +}; + +} + TYPESYSTEM_SOURCE_ABSTRACT(Data::Segment , Base::BaseClass) @@ -55,17 +1218,15 @@ ComplexGeoData::~ComplexGeoData() Data::Segment* ComplexGeoData::getSubElementByName(const char* name) const { - int index = 0; - std::string element; - boost::regex ex("^([^0-9]*)([0-9]*)$"); - boost::cmatch what; - - if (boost::regex_match(name, what, ex)) { - element = what[1].str(); - index = std::atoi(what[2].str().c_str()); - } - - return getSubElement(element.c_str(), static_cast(index)); + int index = 0; + std::string element(name); + std::string::size_type pos = element.find_first_of("0123456789"); + if (pos != std::string::npos) { + index = std::atoi(element.substr(pos).c_str()); + element = element.substr(0,pos); + } + + return getSubElement(element.c_str(),index); } void ComplexGeoData::applyTransform(const Base::Matrix4D& rclTrf) @@ -174,11 +1335,22 @@ const char *ComplexGeoData::isMappedElement(const char *name) { return nullptr; } +std::string ComplexGeoData::getElementMapVersion() const { + return "4"; +} + +bool ComplexGeoData::checkElementMapVersion(const char * ver) const +{ + return !boost::equals(ver, "3") + && !boost::equals(ver, "4") + && !boost::starts_with(ver, "3."); +} + std::string ComplexGeoData::newElementName(const char *name) { - if(!name) + if(!name) return std::string(); const char *dot = strrchr(name,'.'); - if(!dot || dot==name) + if(!dot || dot==name) return name; const char *c = dot-1; for(;c!=name;--c) { @@ -193,10 +1365,10 @@ std::string ComplexGeoData::newElementName(const char *name) { } std::string ComplexGeoData::oldElementName(const char *name) { - if(!name) + if(!name) return std::string(); const char *dot = strrchr(name,'.'); - if(!dot || dot==name) + if(!dot || dot==name) return name; const char *c = dot-1; for(;c!=name;--c) { @@ -211,7 +1383,7 @@ std::string ComplexGeoData::oldElementName(const char *name) { } std::string ComplexGeoData::noElementName(const char *name) { - if(!name) + if(!name) return std::string(); auto element = findElementName(name); if(element) @@ -220,6 +1392,9 @@ std::string ComplexGeoData::noElementName(const char *name) { } const char *ComplexGeoData::findElementName(const char *subname) { + // skip leading dots + while(subname && subname[0] == '.') + ++subname; if(!subname || !subname[0] || isMappedElement(subname)) return subname; const char *dot = strrchr(subname,'.'); @@ -239,11 +1414,324 @@ const char *ComplexGeoData::findElementName(const char *subname) { return element; } +size_t ComplexGeoData::getElementMapSize(bool flush) const { + if (flush) { + flushElementMap(); +#ifdef _FC_MEM_TRACE + FC_MSG("memory size " << (_MemSize/1024/1024) << "MB, " << (_MemMaxSize/1024/1024)); + for (auto &unit : _MemUnits) + FC_MSG("unit " << unit.first << ": " << unit.second.count << ", " << unit.second.maxcount); +#endif + } + return _ElementMap?_ElementMap->size():0; +} + +MappedName ComplexGeoData::getMappedName(const IndexedName & element, + bool allowUnmapped, + ElementIDRefs *sid) const +{ + if (!element) + return MappedName(); + flushElementMap(); + if(!_ElementMap) { + if (allowUnmapped) + return MappedName(element); + return MappedName(); + } + + MappedName name = _ElementMap->find(element, sid); + if (allowUnmapped && !name) + return MappedName(element); + return name; +} + +IndexedName ComplexGeoData::getIndexedName(const MappedName & name, + ElementIDRefs *sid) const +{ + flushElementMap(); + if (!name) + return IndexedName(); + if (!_ElementMap) { + std::string s; + return IndexedName(name.toString(s), getElementTypes()); + } + return _ElementMap->find(name, sid); +} + +Data::MappedElement +ComplexGeoData::getElementName(const char *name, + ElementIDRefs *sid, + bool copy) const +{ + IndexedName element(name, getElementTypes()); + if (element) + return MappedElement(getMappedName(element, false, sid), element); + + const char * mapped = isMappedElement(name); + if (mapped) + name = mapped; + + MappedElement res; + // Strip out the trailing '.XXXX' if any + const char *dot = strchr(name,'.'); + if(dot) + res.name = MappedName(name, dot-name); + else if (copy) + res.name = name; + else + res.name = MappedName(name); + res.index = getIndexedName(res.name, sid); + return res; +} + +std::vector > +ComplexGeoData::getElementMappedNames(const IndexedName & element, bool needUnmapped) const { + flushElementMap(); + if(_ElementMap) { + auto res = _ElementMap->findAll(element); + if (!res.empty()) + return res; + } + + if (!needUnmapped) + return {}; + return {std::make_pair(MappedName(element), ElementIDRefs())}; +} + +std::vector +ComplexGeoData::getElementNamesWithPrefix(const char *prefix) const { +#if 0 + std::vector names; + flushElementMap(); + if(!prefix || !prefix[0] || !_ElementMap) + return names; + const auto &p = elementMapPrefix(); + if(boost::starts_with(prefix,p)) + prefix += p.size(); + names = _ElementMap->findAllStartsWith(prefix); + return names; +#else + (void)prefix; + return {}; +#endif +} + +std::vector ComplexGeoData::getElementMap() const { + flushElementMap(); + if(!_ElementMap) + return {}; + return _ElementMap->getAll(); +} + +ElementMapPtr ComplexGeoData::elementMap(bool flush) const +{ + if (flush) + flushElementMap(); + return _ElementMap; +} + +void ComplexGeoData::flushElementMap() const +{ +} + +void ComplexGeoData::setElementMap(const std::vector &map) { + resetElementMap(); + for(auto &v : map) + setElementName(v.index, v.name); +} + +MappedName ComplexGeoData::hashElementName( + const MappedName & name, ElementIDRefs &sids) const +{ + if(!this->Hasher || !name) + return name; + if (name.find(elementMapPrefix()) < 0) + return name; + App::StringIDRef sid = this->Hasher->getID(name, sids); + const auto &related = sid.relatedIDs(); + if (related == sids) { + sids.clear(); + sids.push_back(sid); + } else { + ElementIDRefs tmp; + tmp.push_back(sid); + for (auto &s : sids) { + if (related.indexOf(s) < 0) + tmp.push_back(s); + } + sids = tmp; + } + return MappedName(sid.toString()); +} + +MappedName ComplexGeoData::dehashElementName(const MappedName & name) const { + if(name.empty()) + return name; + if(!Hasher) + return name; + auto id = App::StringID::fromString(name.toRawBytes()); + if(!id) + return name; + auto sid = Hasher->getID(id); + if(!sid) { + if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) + FC_WARN("failed to find hash id " << id); + else + FC_LOG("failed to find hash id " << id); + return name; + } + if(sid.isHashed()) { + FC_LOG("cannot dehash id " << id); + return name; + } + MappedName ret(sid); + FC_TRACE("dehash " << name << " -> " << ret); + return ret; +} + +MappedName ComplexGeoData::setElementName(const IndexedName & element, + const MappedName & name, + const ElementIDRefs *sid, + bool overwrite) +{ + if(!element) + throw Base::ValueError("Invalid input"); + if(!name) { + if(_ElementMap) + _ElementMap->erase(element); + return MappedName(); + } + + for(int i=0, count=name.size(); i()); + + ElementIDRefs _sid; + if (!sid) + sid = &_sid; + + std::ostringstream ss; + Data::MappedName n(name); + for(int i=0;;) { + IndexedName existing; + MappedName res = _ElementMap->addName(n, element, *sid, overwrite, &existing); + if (res) + return res; + if (++i == 100) { + FC_ERR("unresolved duplicate element mapping '" << name + <<' ' << element << '/' << existing); + return name; + } + if(sid != &_sid) + _sid = *sid; + n = renameDuplicateElement(i,element,existing,name,_sid); + if (!n) + return name; + sid = &_sid; + } + +} + +char ComplexGeoData::elementType(const Data::MappedName &name) const +{ + if(!name) + return 0; + auto indexedName = getIndexedName(name); + if (indexedName) + return elementType(indexedName); + char element_type=0; + if (findTagInElementName(name,0,0,0,&element_type) < 0) + return elementType(name.toIndexedName()); + return element_type; +} + +char ComplexGeoData::elementType(const Data::IndexedName &element) const +{ + if(!element) + return 0; + for(auto &type : getElementTypes()) { + if(boost::equals(element.getType(), type)) + return type[0]; + } + return 0; +} + +char ComplexGeoData::elementType(const char *name) const { + if(!name) + return 0; + + const char *type = nullptr; + IndexedName element(name, getElementTypes()); + if (element) + type = element.getType(); + else { + const char * mapped = isMappedElement(name); + if (mapped) + name = mapped; + + MappedName n; + const char *dot = strchr(name,'.'); + if(dot) { + n = MappedName(name, dot-name); + type = dot+1; + } + else + n = MappedName::fromRawData(name); + char res = elementType(n); + if (res) + return res; + } + + if(type && type[0]) { + for(auto &t : getElementTypes()) { + if(boost::starts_with(type, t)) + return type[0]; + } + } + return 0; +} + +MappedName ComplexGeoData::renameDuplicateElement(int index, + const IndexedName & element, + const IndexedName & element2, + const MappedName & name, + ElementIDRefs &sids) +{ + std::ostringstream ss; + ss << elementMapPrefix() << 'D' << std::hex << index; + MappedName renamed(name); + encodeElementName(element.getType()[0],renamed,ss,&sids); + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("duplicate element mapping '" << name << " -> " << renamed << ' ' + << element << '/' << element2); + return renamed; +} + const std::string &ComplexGeoData::tagPostfix() { + static std::string postfix(elementMapPrefix() + ":H"); + return postfix; +} + +const std::string &ComplexGeoData::decTagPostfix() { static std::string postfix(elementMapPrefix() + ":T"); return postfix; } +const std::string &ComplexGeoData::externalTagPostfix() { + static std::string postfix(elementMapPrefix() + ":X"); + return postfix; +} + const std::string &ComplexGeoData::indexPostfix() { static std::string postfix(elementMapPrefix() + ":I"); return postfix; @@ -263,9 +1751,599 @@ bool ComplexGeoData::hasMissingElement(const char *subname) { return boost::starts_with(subname,missingPrefix()); } +int ComplexGeoData::findTagInElementName(const MappedName & name, + long *tag, + int *len, + std::string *postfix, + char *type, + bool negative, + bool recursive) +{ + bool hex = true; + int pos = name.rfind(tagPostfix()); + + // Example name, tagPosfix == ;:H + // #94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F + // ^ + // | + // pos + + if(pos < 0) { + pos = name.rfind(decTagPostfix()); + if (pos < 0) + return -1; + hex = false; + } + int offset = pos + (int)tagPostfix().size(); + long _tag = 0; + int _len = 0; + char sep = 0; + char sep2 = 0; + char tp = 0; + char eof = 0; + + int size; + const char *s = name.toConstString(offset, size); + + // check if the number followed by the tagPosfix is negative + bool isNegative = (s[0] == '-'); + if (isNegative) { + ++s; + --size; + } + bio::stream 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 elementMapPrefix(). + // + // For newer tagPostfix(), 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(name, pos-_len, _len).rfind(tagPostfix()); + 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( + name, next+1, pos-next-1).find(elementMapPrefix()); + 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 findTagInElementName( + MappedName(name, 0, _len), tag, len, postfix, type, negative); + if(_tag>0 || negative) + *tag = _tag; + else + *tag = -_tag; + } + if(len) + *len = _len; + if(postfix) + name.toString(*postfix, pos); + return pos; +} + +// try to hash element name while preserving the source tag +void ComplexGeoData::encodeElementName(char element_type, + MappedName &name, + std::ostringstream &ss, + ElementIDRefs *sids, + const char* postfix, + long tag, + bool forceTag) const +{ + if(postfix && postfix[0]) { + if (!boost::starts_with(postfix, elementMapPrefix())) + ss << elementMapPrefix(); + ss << postfix; + } + long inputTag = 0; + if (!forceTag && !ss.tellp()) { + if(!tag || tag==Tag) + return; + findTagInElementName(name,&inputTag,nullptr,nullptr,nullptr,true); + if(inputTag == tag) + return; + } + else if (!tag || (!forceTag && tag==Tag)) { + int pos = findTagInElementName(name,&inputTag,nullptr,nullptr,nullptr,true); + if(inputTag) { + tag = inputTag; + // About to encode the same tag used last time. This usually means + // the owner object is doing multi step modeling. Let's not + // recursively encode the same tag too many time. It will be a + // waste of memory, because the intermediate shapes has no + // corresponding objects, so no real value for history tracing. + // + // On the other hand, we still need to distinguish the original name + // from the input object from the element name of the intermediate + // shapes. So we limit ourselves to encode only one extra level + // using the same tag. In order to do that, we need to dehash the + // previous level name, and check for its tag. + Data::MappedName n(name, 0, pos); + Data::MappedName prev = dehashElementName(n); + long prevTag = 0; + findTagInElementName(prev,&prevTag,nullptr,nullptr,nullptr,true); + if (prevTag == inputTag || prevTag == -inputTag) + name = n; + } + } + + if(sids && Hasher) { + name = hashElementName(name, *sids); + if (!forceTag && !tag && ss.tellp()) + forceTag = true; + } + if(forceTag || tag) { + assert(element_type); + int pos = ss.tellp(); + boost::io::ios_flags_saver ifs(ss); + ss << tagPostfix() << std::hex; + if (tag < 0) + ss << '-' << -tag; + else if (tag) + ss << tag; + assert(pos >= 0); + if (pos != 0) + ss << ':' << pos; + ss << ',' << element_type; + } + name += ss.str(); +} + +long ComplexGeoData::getElementHistory(const char *name, + MappedName *original, + std::vector *history) const +{ + MappedElement mapped = getElementName(name); + if (!mapped.name) + return 0; + return getElementHistory(mapped.name, original, history); +} + +long ComplexGeoData::getElementHistory(const MappedName & name, + MappedName *original, + std::vector *history) const +{ + long tag = 0; + int len = 0; + int pos = findTagInElementName(name,&tag,&len,nullptr,nullptr,true); + if(pos < 0) { + if(original) + *original = name; + return tag; + } + if(!original && !history) + return tag; + + MappedName tmp; + MappedName &ret = original?*original:tmp; + if(name.startsWith(elementMapPrefix())) { + unsigned offset = elementMapPrefix().size(); + ret = MappedName::fromRawData(name, offset); + } else + ret = name; + + while(1) { + if(!len || len>pos) { + FC_WARN("invalid name length " << name); + return 0; + } + bool dehashed = false; + if (ret.startsWith(MappedChildElements::prefix(), len)) { + int offset = (int)MappedChildElements::prefix().size(); + MappedName tmp = MappedName::fromRawData(ret, len+offset, pos-len-offset); + MappedName postfix = dehashElementName(tmp); + if (postfix != tmp) { + dehashed = true; + ret = MappedName::fromRawData(ret, 0, len) + postfix; + } + } + if (!dehashed) + ret = dehashElementName(MappedName::fromRawData(ret, 0, len)); + + long tag2 = 0; + pos = findTagInElementName(ret,&tag2,&len,nullptr,nullptr,true); + if(pos < 0 || (tag2!=tag && tag2!=-tag && tag!=Tag && -tag!=Tag)) + return tag; + tag = tag2; + if(history) + history->push_back(ret.copy()); + } +} + void ComplexGeoData::setPersistenceFileName(const char *filename) const { if(!filename) filename = ""; _PersistenceName = filename; } +void ComplexGeoData::Save(Base::Writer &writer) const { + + if(!getElementMapSize()) { + writer.Stream() << writer.ind() << "\n"; + return; + } + + // Store some dummy map entry to trigger recompute in older version. + writer.Stream() << writer.ind() + << "" + << "" + << "\n"; + + // New layout of element map, so we use new xml tag, ElementMap2 + writer.Stream() << writer.ind() << "\n"; + return; + } + writer.Stream() << " count=\"" << _ElementMap->size() << "\">\n"; + _ElementMap->save(writer.beginCharStream(false) << '\n'); + writer.endCharStream() << '\n'; + writer.Stream() << writer.ind() << "\n" ; +} + +void ComplexGeoData::Restore(Base::XMLReader &reader) { + resetElementMap(); + + reader.readElement("ElementMap"); + bool newtag = false; + if (reader.getAttributeAsInteger("new","0") > 0) { + reader.readEndElement("ElementMap"); + reader.readElement("ElementMap2"); + newtag = true; + } + + const char *file = reader.getAttribute("file",""); + if(*file) { + reader.addFile(file,this); + return; + } + + std::size_t count = reader.getAttributeAsUnsigned("count",""); + if(!count) + return; + + if (newtag) { + resetElementMap(std::make_shared()); + _ElementMap = _ElementMap->restore(Hasher, reader.beginCharStream(false)); + reader.endCharStream(); + reader.readEndElement("ElementMap2"); + return; + } + + if(reader.FileVersion>1) { + restoreStream(reader.beginCharStream(false), count); + reader.endCharStream(); + return; + } + + size_t invalid_count = 0; + bool warned = false; + + const auto & types = getElementTypes(); + + for(size_t i=0;i iss(attr, std::strlen(attr)); + long id; + while((iss >> id)) { + if (id == 0) + continue; + auto sid = Hasher->getID(id); + if(!sid) + ++invalid_count; + else + sids.push_back(sid); + char sep; + iss >> sep; + } + } + } + setElementName(IndexedName(reader.getAttribute("value"), types), + MappedName(reader.getAttribute("key")), + &sids); + } + if(invalid_count) + FC_ERR("Found " << invalid_count << " invalid string id"); + reader.readEndElement("ElementMap"); +} + +void ComplexGeoData::restoreStream(std::istream &s, std::size_t count) { + resetElementMap(); + + size_t invalid_count = 0; + std::string key,value,sid; + bool warned = false; + + const auto & types = getElementTypes(); + try { + for(size_t i=0;i> value >> key >> scount)) + FC_THROWM(Base::RuntimeError, + "Failed to restore element map " << _PersistenceName); + sids.reserve(scount); + for(std::size_t j=0;j> id)) + FC_THROWM(Base::RuntimeError, + "Failed to restore element map " << _PersistenceName); + if (Hasher) { + auto sid = Hasher->getID(id); + if(!sid) + ++invalid_count; + else + sids.push_back(sid); + } + } + if(scount && !Hasher) { + sids.clear(); + if(!warned) { + warned = true; + FC_ERR("missing hasher"); + } + } + setElementName(IndexedName(value.c_str(), types), MappedName(key), &sids); + } + } catch (Base::Exception &e) { + e.ReportException(); + _restoreFailed = true; + _ElementMap.reset(); + } + if(invalid_count) + FC_ERR("Found " << invalid_count << " invalid string id"); +} + +void ComplexGeoData::SaveDocFile(Base::Writer &writer) const { + flushElementMap(); + if (_ElementMap) { + writer.Stream() << "BeginElementMap v1\n"; + _ElementMap->save(writer.Stream()); + } +} + +void ComplexGeoData::RestoreDocFile(Base::Reader &reader) { + std::string marker, ver; + reader >> marker; + if (boost::equals(marker, "BeginElementMap")) { + resetElementMap(); + reader >> ver; + if (ver != "v1") + FC_WARN("Unknown element map format"); + else { + resetElementMap(std::make_shared()); + _ElementMap = _ElementMap->restore(Hasher, reader); + return; + } + } + std::size_t count = atoi(marker.c_str()); + restoreStream(reader,count); +} + +unsigned int ComplexGeoData::getMemSize(void) const { + flushElementMap(); + if(_ElementMap) + return _ElementMap->size()*10; + return 0; +} + +std::vector ComplexGeoData::getHigherElements(const char *, bool) const +{ + return {}; +} + +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 tagSet; + + std::vector 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 accidently + // 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 & children) +{ + // DO NOT reset element map if there is one. Because we allow mixing child + // mapping and normal mapping + if (!_ElementMap) + resetElementMap(std::make_shared()); + + _ElementMap->addChildElements(*this, children); +} + +std::vector ComplexGeoData::getMappedChildElements() const +{ + if (!_ElementMap) + return {}; + return _ElementMap->getChildElements(); +} + +void ComplexGeoData::beforeSave() const +{ + flushElementMap(); + if (this->_ElementMap) + this->_ElementMap->beforeSave(Hasher); +} + +void ComplexGeoData::hashChildMaps() +{ + flushElementMap(); + if (_ElementMap) + _ElementMap->hashChildMaps(*this); +} + +bool ComplexGeoData::hasChildElementMap() const +{ + flushElementMap(); + return _ElementMap && _ElementMap->hasChildElementMap(); +} diff --git a/src/App/ComplexGeoData.h b/src/App/ComplexGeoData.h index d7ada96ead..721f2f59fc 100644 --- a/src/App/ComplexGeoData.h +++ b/src/App/ComplexGeoData.h @@ -24,9 +24,17 @@ #ifndef _AppComplexGeoData_h_ #define _AppComplexGeoData_h_ +#include +#include +#include +#include + +#include + #include #include #include +#include "StringHasher.h" #ifdef __GNUC__ # include @@ -44,6 +52,16 @@ typedef BoundBox3 BoundBox3d; namespace Data { +class ElementMap; +typedef std::shared_ptr ElementMapPtr; + +typedef QVector ElementIDRefs; + +class IndexedName; +class MappedName; +struct MappedElement; +struct MappedChildElements; + /** Segments * Subelement type of the ComplexGeoData type * It is used to split an object in further sub-parts. @@ -165,8 +183,12 @@ public: //@{ /// Special prefix to mark the beginning of a mapped sub-element name static const std::string &elementMapPrefix(); - /// Special postfix to mark the following tag + /// Special postfix to mark the following tag encoded as hex number static const std::string &tagPostfix(); + /// Special postfix to mark the following tag encoded as decimal number + static const std::string &decTagPostfix(); + /// Special postfix to mark the name includes encoding from an external object + static const std::string &externalTagPostfix(); /// Special postfix to mark the index of an array element static const std::string &indexPostfix(); /// Special prefix to mark a missing element @@ -190,18 +212,247 @@ public: /// Find the start of an element name in a subname static const char *findElementName(const char *subname); + + /// Check if the given subname contains element name + static bool hasElementName(const char *subname) { + subname = findElementName(subname); + return subname && *subname; + } + /// Return the element name portion of the subname without mapping prefix static inline const char *hasMappedElementName(const char *subname) { return isMappedElement(findElementName(subname)); } + + /** Get element indexed name + * + * @param name: the input name + * @param 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 + * + * @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. + */ + 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 and App::StringID involved 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. + * + * 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 + * name contains only alphabets and underscore followed by optional digits, + * it will be treated as indexed name. Or else, it will be treated as + * mapped name. + */ + MappedElement getElementName(const char * name, + ElementIDRefs *sid = nullptr, + bool copy = false) const; + + /** Get mapped element with a given prefix */ + std::vector getElementNamesWithPrefix(const char *prefix) const; + + /** 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 + * + * @return a list of mapped names of the give element along with their + * associated string ID references + */ + std::vector > + getElementMappedNames(const IndexedName & element, bool needUnmapped=false) const; + + /** 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 + */ + MappedName setElementName(const IndexedName & element, + const MappedName & name, + const ElementIDRefs * sid = nullptr, + bool overwrite = false); + + void setMappedChildElements(const std::vector & children); + std::vector getMappedChildElements() const; + + /** 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 &sid) const; + + /// Hash the child element map postfixes to shorten element name from hierarchical maps + void hashChildMaps(); + + /// Check if there is child element map + bool hasChildElementMap() const; + + /// Reverse hashElementName() + MappedName dehashElementName(const MappedName & name) const; + + /// Append the Tag (if and only if it is non zero) into the element map + virtual void reTagElementMap(long tag, App::StringHasherRef hasher, const char *postfix=0) { + (void)tag; + (void)hasher; + (void)postfix; + } + + long getElementHistory(const char *name, + MappedName *original=0, std::vector *history=0) const; + + long getElementHistory(const MappedName & name, + MappedName *original=0, std::vector *history=0) const; + + void encodeElementName(char element_type, MappedName & name, std::ostringstream &ss, + ElementIDRefs *sids, const char* postfix=0, long tag=0, bool forceTag=false) const; + + char elementType(const Data::MappedName &) const; + char elementType(const Data::IndexedName &) const; + char elementType(const char *name) const; + + /** Reset/swap the element map + * + * @param elementMap: optional new element map + * + * @return Returns the existing element map. + */ + virtual ElementMapPtr resetElementMap(ElementMapPtr elementMap=ElementMapPtr()) { + _ElementMap.swap(elementMap); + return elementMap; + } + + /// Get the entire element map + std::vector getElementMap() const; + + /// Set the entire element map + void setElementMap(const std::vector &elements); + + /// Get the current element map size + size_t getElementMapSize(bool flush=true) const; + + /// Return the higher level element names of the given element + virtual std::vector getHigherElements(const char *name, bool silent=false) const; + + /// Return the current element map version + virtual std::string getElementMapVersion() const; + + /// Return true to signal element map version change + virtual bool checkElementMapVersion(const char * ver) const; + + /// Check if the given subname only contains an element name + static bool isElementName(const char *subname) { + return subname && *subname && findElementName(subname)==subname; + } + + /** Extract tag and other information from a encoded element name + * + * @param name: 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. + */ + static int findTagInElementName(const MappedName & name, + long *tag=0, + int *len=0, + std::string *postfix=0, + char *type=0, + bool negative=false, + bool recursive=true); + + /** 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 TraceCallback; + + /** 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, TraceCallback cb) const; + + /** Flush an internal buffering for element mapping */ + virtual void flushElementMap() const; + virtual unsigned long getElementMapReserve() const { return 0; } //@} /** @name Save/restore */ //@{ + void Save (Base::Writer &writer) const; + void Restore(Base::XMLReader &reader); + void SaveDocFile(Base::Writer &writer) const; + void RestoreDocFile(Base::Reader &reader); + unsigned int getMemSize (void) const; void setPersistenceFileName(const char *name) const; + virtual void beforeSave() const; + bool isRestoreFailed() const { return _restoreFailed; } + void resetRestoreFailure() const { _restoreFailed = true; } //@} +public: + /// String hasher for element name shortening + mutable App::StringHasherRef Hasher; + protected: + virtual MappedName renameDuplicateElement(int index, + const IndexedName & element, + const IndexedName & element2, + const MappedName & name, + ElementIDRefs &sids); + + void restoreStream(std::istream &s, std::size_t count); /// from local to outside inline Base::Vector3d transformToOutside(const Base::Vector3f& vec) const @@ -223,9 +474,16 @@ protected: public: mutable long Tag; + +protected: + ElementMapPtr elementMap(bool flush=true) const; + protected: mutable std::string _PersistenceName; + mutable bool _restoreFailed = false; +private: + ElementMapPtr _ElementMap; }; } //namespace App diff --git a/src/App/ComplexGeoDataPy.xml b/src/App/ComplexGeoDataPy.xml index 2202ccf8d8..65afdee19e 100644 --- a/src/App/ComplexGeoDataPy.xml +++ b/src/App/ComplexGeoDataPy.xml @@ -64,6 +64,37 @@ Apply a transformation to the underlying geometry + + + +setElementName(element,name=None,postfix=None,overwrite=False,sid=None), Set an element name + +element : the original element name, e.g. Edge1, Vertex2 +name : the new name for the element, None to remove the mapping +postfix : postfix of the name that will not be hashed +overwrite: if true, it will overwrite exiting name +sid : to hash the name any way you want, provide your own string id(s) in this parameter + +An element can have multiple mapped names. However, a name can only be mapped +to one element + + + + + + getElementName(name,direction=0) - Return a mapped element name or reverse + + + + + getElementIndexedName(name) - Return the indexed element name + + + + + getElementMappedName(name) - Return the mapped element name + + Get the BoundBox of the object @@ -88,5 +119,35 @@ + + + Get/Set the string hasher of this object + + + + + + Get the current element map size + + + + + + Get/Set a dict of element mapping + + + + + + Get a dict of element reverse mapping + + + + + + Element map version + + + diff --git a/src/App/ComplexGeoDataPyImp.cpp b/src/App/ComplexGeoDataPyImp.cpp index b7dd72f234..21633e5168 100644 --- a/src/App/ComplexGeoDataPyImp.cpp +++ b/src/App/ComplexGeoDataPyImp.cpp @@ -27,6 +27,7 @@ #endif #include "ComplexGeoData.h" +#include "MappedElement.h" // inclusion of the generated files (generated out of ComplexGeoDataPy.xml) #include @@ -36,6 +37,8 @@ #include #include #include +#include +#include using namespace Data; using namespace Base; @@ -51,10 +54,9 @@ PyObject* ComplexGeoDataPy::getElementTypes(PyObject *args) if (!PyArg_ParseTuple(args, "")) return nullptr; - std::vector types = getComplexGeoDataPtr()->getElementTypes(); Py::List list; - for (auto it : types) { - list.append(Py::String(it)); + for (const auto &type : getComplexGeoDataPtr()->getElementTypes()) { + list.append(Py::String(type)); } return Py::new_reference_to(list); } @@ -302,6 +304,183 @@ PyObject* ComplexGeoDataPy::transformGeometry(PyObject *args) } } +PyObject* ComplexGeoDataPy::getElementName(PyObject *args) +{ + char* input; + int direction = 0; + if (!PyArg_ParseTuple(args, "s|i", &input,&direction)) + return NULL; + + Data::MappedElement res = getComplexGeoDataPtr()->getElementName(input); + std::string s; + if (direction == 1) + return Py::new_reference_to(Py::String(res.name.toString(s))); + else if (direction == 0) + return Py::new_reference_to(Py::String(res.index.toString(s))); + else if (Data::IndexedName(input)) + return Py::new_reference_to(Py::String(res.name.toString(s))); + else + return Py::new_reference_to(Py::String(res.index.toString(s))); +} + +PyObject* ComplexGeoDataPy::getElementIndexedName(PyObject *args) +{ + char* input; + PyObject *returnID = Py_False; + if (!PyArg_ParseTuple(args, "s|O", &input,&returnID)) + return NULL; + + ElementIDRefs ids; + Data::MappedElement res = getComplexGeoDataPtr()->getElementName( + input, PyObject_IsTrue(returnID)?&ids:nullptr); + std::string s; + Py::String name(res.index.toString(s)); + if (!PyObject_IsTrue(returnID)) + return Py::new_reference_to(name); + + Py::List list; + for (auto &id : ids) + list.append(Py::Long(id.value())); + return Py::new_reference_to(Py::TupleN(name, list)); +} + +PyObject* ComplexGeoDataPy::getElementMappedName(PyObject *args) +{ + char* input; + PyObject *returnID = Py_False; + if (!PyArg_ParseTuple(args, "s|O", &input,&returnID)) + return NULL; + + ElementIDRefs ids; + Data::MappedElement res = getComplexGeoDataPtr()->getElementName( + input, PyObject_IsTrue(returnID)?&ids:nullptr); + std::string s; + Py::String name(res.name.toString(s)); + if (!PyObject_IsTrue(returnID)) + return Py::new_reference_to(name); + + Py::List list; + for (auto &id : ids) + list.append(Py::Long(id.value())); + return Py::new_reference_to(Py::TupleN(name, list)); +} + +PyObject *ComplexGeoDataPy::setElementName(PyObject *args, PyObject *kwds) { + const char *element; + const char *name = 0; + const char *postfix = 0; + int tag = 0; + PyObject *pySid = Py_None; + PyObject *overwrite = Py_False; + + static char *kwlist[] = {"element", "name", "postfix", "overwrite", "sid", "tag", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|sssOOi", kwlist, + &element,&name,&postfix,&overwrite,&pySid,&tag)) + return NULL; + ElementIDRefs sids; + if(pySid != Py_None) { + if(PyObject_TypeCheck(pySid,&App::StringIDPy::Type)) + sids.push_back(static_cast(pySid)->getStringIDPtr()); + else if(PySequence_Check(pySid)) { + Py::Sequence seq(pySid); + for(auto it=seq.begin();it!=seq.end();++it) { + auto ptr = (*it).ptr(); + if(PyObject_TypeCheck(ptr,&App::StringIDPy::Type)) + sids.push_back(static_cast(ptr)->getStringIDPtr()); + else + throw Py::TypeError("expect StringID in sid sequence"); + } + } else + throw Py::TypeError("expect sid to contain either StringID or sequence of StringID"); + } + PY_TRY { + Data::IndexedName index(element, getComplexGeoDataPtr()->getElementTypes()); + Data::MappedName mapped = Data::MappedName::fromRawData(name); + std::ostringstream ss; + getComplexGeoDataPtr()->encodeElementName(getComplexGeoDataPtr()->elementType(index), + mapped, ss, &sids, postfix, tag); + Data::MappedName res = getComplexGeoDataPtr()->setElementName( + index, mapped, &sids, PyObject_IsTrue(overwrite)); + return Py::new_reference_to(Py::String(res.toString(0))); + }PY_CATCH +} + +Py::Object ComplexGeoDataPy::getHasher() const { + auto self = getComplexGeoDataPtr(); + if(!self->Hasher) + return Py::None(); + return Py::Object(self->Hasher->getPyObject(),true); +} + +Py::Dict ComplexGeoDataPy::getElementMap() const { + Py::Dict ret; + std::string s; + for(auto &v : getComplexGeoDataPtr()->getElementMap()) { + s.clear(); + ret.setItem(v.name.toString(0), Py::String(v.index.toString(s))); + } + return ret; +} + +void ComplexGeoDataPy::setElementMap(Py::Dict dict) { + std::vector map; + const auto & types = getComplexGeoDataPtr()->getElementTypes(); + for(auto it=dict.begin();it!=dict.end();++it) { + const auto &value = *it; + if(!value.first.isString() || !value.second.isString()) + throw Py::TypeError("expect only strings in the dict"); + map.emplace_back(Data::MappedName(value.first.as_string().c_str()), + Data::IndexedName(Py::Object(value.second).as_string().c_str(), types)); + } + getComplexGeoDataPtr()->setElementMap(map); +} + +Py::Dict ComplexGeoDataPy::getElementReverseMap() const { + Py::Dict ret; + std::string s; + for(auto &v : getComplexGeoDataPtr()->getElementMap()) { + s.clear(); + auto value = ret[Py::String(v.index.toString(s))]; + Py::Object item(value); + if(item.isNone()) { + s.clear(); + value = Py::String(v.name.toString(s)); + } else if(item.isList()) { + Py::List list(item); + s.clear(); + list.append(Py::String(v.name.toString(s))); + } else { + Py::List list; + list.append(item); + s.clear(); + list.append(Py::String(v.name.toString(s))); + value = list; + } + } + return ret; +} + +Py::Int ComplexGeoDataPy::getElementMapSize() const { + return Py::Int((long)getComplexGeoDataPtr()->getElementMapSize()); +} + +void ComplexGeoDataPy::setHasher(Py::Object obj) { + auto self = getComplexGeoDataPtr(); + if(obj.isNone()) { + if(self->Hasher) { + self->Hasher = App::StringHasherRef(); + self->resetElementMap(); + } + }else if(PyObject_TypeCheck(obj.ptr(),&App::StringHasherPy::Type)) { + App::StringHasherRef ref(static_cast(obj.ptr())->getStringHasherPtr()); + if(self->Hasher != ref) { + self->Hasher = ref; + self->resetElementMap(); + } + }else + throw Py::TypeError("invalid type"); +} + Py::Object ComplexGeoDataPy::getBoundBox() const { return Py::BoundingBox(getComplexGeoDataPtr()->getBoundBox()); @@ -334,6 +513,11 @@ void ComplexGeoDataPy::setPlacement(Py::Object arg) } } +Py::String ComplexGeoDataPy::getElementMapVersion() const +{ + return Py::String(getComplexGeoDataPtr()->getElementMapVersion()); +} + Py::Int ComplexGeoDataPy::getTag() const { return Py::Int(getComplexGeoDataPtr()->Tag); diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index cc1953694f..dac4a28daa 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -1270,6 +1270,20 @@ bool DocumentObject::adjustRelativeLinks( return touched; } +std::string DocumentObject::getElementMapVersion(const App::Property *_prop, bool restored) const { + auto prop = Base::freecad_dynamic_cast(_prop); + if(!prop) + return std::string(); + return prop->getElementMapVersion(restored); +} + +bool DocumentObject::checkElementMapVersion(const App::Property *_prop, const char *ver) const { + auto prop = Base::freecad_dynamic_cast(_prop); + if(!prop) + return false; + return prop->checkElementMapVersion(ver); +} + const std::string &DocumentObject::hiddenMarker() { static std::string marker("!hide"); return marker; diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index 9302f56cb7..21c28e8600 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -290,6 +290,20 @@ public: bool testIfLinkDAGCompatible(App::PropertyLinkSubList &linksTo) const; bool testIfLinkDAGCompatible(App::PropertyLinkSub &linkTo) const; + /** Return the element map version of the geometry data stored in the given property + * + * @param prop: the geometry property to query for element map version + * @param restored: whether to query for the restored element map version. + * In case of version upgrade, the restored version may + * be different from the current version. + * + * @return Return the element map version string. + */ + virtual std::string getElementMapVersion(const App::Property *prop, bool restored=false) const; + + /// Return true to signal re-generation of geometry element names + virtual bool checkElementMapVersion(const App::Property *prop, const char *ver) const; + public: /** mustExecute * We call this method to check if the object was modified to diff --git a/src/App/DocumentObserver.cpp b/src/App/DocumentObserver.cpp index dfc4eeac59..dbb429a0f8 100644 --- a/src/App/DocumentObserver.cpp +++ b/src/App/DocumentObserver.cpp @@ -24,10 +24,12 @@ #include "PreCompiled.h" #include - +#include +#include #include "Application.h" #include "ComplexGeoData.h" #include "Document.h" +#include "DocumentObject.h" #include "DocumentObserver.h" #include "GeoFeature.h" @@ -199,6 +201,18 @@ bool DocumentObjectT::operator==(const DocumentObjectT &other) const { && property == other.property; } +bool DocumentObjectT::operator<(const DocumentObjectT &other) const { + if(getDocumentName() < other.getDocumentName()) + return true; + if(getDocumentName() > other.getDocumentName()) + return false; + if(getObjectName() < other.getObjectName()) + return true; + if(getObjectName() > other.getObjectName()) + return false; + return getPropertyName() < other.getPropertyName(); +} + Document* DocumentObjectT::getDocument() const { return GetApplication().getDocument(document.c_str()); @@ -378,22 +392,34 @@ std::string SubObjectT::getNewElementName() const { return std::move(element.first); } -std::string SubObjectT::getOldElementName(int *index) const { - std::pair element; - auto obj = getObject(); - if(!obj) +std::string SubObjectT::getOldElementName(int *index, bool fallback) const { + const char *elementName = Data::ComplexGeoData::findElementName(subname.c_str()); + if (!elementName || !elementName[0]) return std::string(); - GeoFeature::resolveElement(obj,subname.c_str(),element); - if(!index) - return std::move(element.second); - std::size_t pos = element.second.find_first_of("0123456789"); - if(pos == std::string::npos) - *index = -1; - else { - *index = std::atoi(element.second.c_str()+pos); - element.second.resize(pos); + std::string name = Data::ComplexGeoData::oldElementName(elementName); + if (name.empty()) { + std::pair element; + auto obj = getObject(); + if(!obj) + return std::string(); + GeoFeature::resolveElement(obj,subname.c_str(),element); + if (!element.second.empty()) + name = std::move(element.second); + else if (fallback && !element.first.empty()) + name = std::move(element.first); + else + return std::string(); } - return std::move(element.second); + if(index) { + std::size_t pos = name.find_first_of("0123456789"); + if(pos == std::string::npos) + *index = -1; + else { + *index = std::atoi(name.c_str()+pos); + name.resize(pos); + } + } + return name; } App::DocumentObject *SubObjectT::getSubObject() const { @@ -407,8 +433,8 @@ std::string SubObjectT::getSubObjectPython(bool force) const { if(!force && subname.empty()) return getObjectPython(); std::stringstream str; - str << "(" << getObjectPython() << ",u'" - << Base::Tools::escapedUnicodeFromUtf8(subname.c_str()) << "')"; + str << "(" << getObjectPython() << ", '" + << Base::Tools::escapeEncodeString(normalized().subname) << "')"; return str.str(); } diff --git a/src/App/DocumentObserver.h b/src/App/DocumentObserver.h index 2ac41a868f..a4acad42bb 100644 --- a/src/App/DocumentObserver.h +++ b/src/App/DocumentObserver.h @@ -118,6 +118,8 @@ public: void operator=(const Property*); /*! Equality operator */ bool operator==(const DocumentObjectT&) const; + /*! Less than operator */ + bool operator<(const DocumentObjectT &other) const; /*! Get a pointer to the document or 0 if it doesn't exist any more. */ Document* getDocument() const; @@ -230,8 +232,10 @@ public: /** Return the old style sub-element name * @param index: if given, then return the element type, and extract the index + * @param fallback: if true, then fallback to new style element name if + * there is no old style name. */ - std::string getOldElementName(int *index=nullptr) const; + std::string getOldElementName(int *index=nullptr, bool fallback=true) const; /// Return the sub-object DocumentObject *getSubObject() const; diff --git a/src/App/GeoFeature.cpp b/src/App/GeoFeature.cpp index 0015d545d0..92f044bd06 100644 --- a/src/App/GeoFeature.cpp +++ b/src/App/GeoFeature.cpp @@ -25,10 +25,15 @@ #include +#include +#include +#include "ComplexGeoData.h" +#include "Document.h" #include "GeoFeature.h" #include "GeoFeatureGroupExtension.h" -#include "ComplexGeoData.h" +#include "MappedElement.h" +FC_LOG_LEVEL_INIT("GeoFeature",true,true) using namespace App; @@ -43,6 +48,8 @@ PROPERTY_SOURCE(App::GeoFeature, App::DocumentObject) GeoFeature::GeoFeature(void) { ADD_PROPERTY_TYPE(Placement,(Base::Placement()),nullptr,Prop_NoRecompute,nullptr); + ADD_PROPERTY_TYPE(_ElementMapVersion,(""),"Base", + (App::PropertyType)(Prop_Output|Prop_Hidden|Prop_Transient),""); } GeoFeature::~GeoFeature(void) @@ -81,16 +88,48 @@ PyObject* GeoFeature::getPyObject(void) } -std::pair GeoFeature::getElementName( - const char *name, ElementNameType type) const +std::pair +GeoFeature::getElementName(const char *name, ElementNameType type) const { (void)type; std::pair ret; - if(!name) + if(!name) return ret; - ret.second = name; + auto prop = getPropertyOfGeometry(); + if(!prop) return std::make_pair("", name); + + auto geo = prop->getComplexData(); + if(!geo) return std::make_pair("", name); + + return _getElementName(name, geo->getElementName(name)); +} + +std::pair +GeoFeature::_getElementName(const char *name, const Data::MappedElement &mapped) const +{ + std::pair ret; + if (mapped.index && mapped.name) { + std::ostringstream ss; + ss << Data::ComplexGeoData::elementMapPrefix() + << mapped.name << '.' << mapped.index; + ret.first = ss.str(); + mapped.index.toString(ret.second); + } else if (mapped.name) { + FC_TRACE("element mapped name " << name << " not found in " << getFullName()); + ret.first = name; + const char *dot = strrchr(name,'.'); + if(dot) { + // deliberately mangle the old style element name to signal a + // missing reference + ret.second = Data::ComplexGeoData::missingPrefix(); + ret.second += dot+1; + } + } else { + mapped.index.toString(ret.second); + } + return ret; } @@ -99,6 +138,8 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn ElementNameType type, const DocumentObject *filter, const char **_element, GeoFeature **geoFeature) { + elementName.first.clear(); + elementName.second.clear(); if(!obj || !obj->getNameInDocument()) return nullptr; if(!subname) @@ -108,11 +149,16 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn auto sobj = obj->getSubObject(subname); if(!sobj) return nullptr; - obj = sobj->getLinkedObject(true); - auto geo = dynamic_cast(obj); + auto linked = sobj->getLinkedObject(true); + auto geo = Base::freecad_dynamic_cast(linked); + if(!geo && linked) { + auto ext = linked->getExtensionByType(true); + if(ext) + geo = Base::freecad_dynamic_cast(ext->getTrueLinkedObject(true)); + } if(geoFeature) *geoFeature = geo; - if(!obj || (filter && obj!=filter)) + if(filter && geo!=filter) return nullptr; if(!element || !element[0]) { if(append) @@ -139,3 +185,78 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn return sobj; } +bool GeoFeature::hasMissingElement(const char *subname) { + return Data::ComplexGeoData::hasMissingElement(subname); + if(!subname) + return false; + auto dot = strrchr(subname,'.'); + if(!dot) + return subname[0]=='?'; + return dot[1]=='?'; +} + +void GeoFeature::updateElementReference() { + auto prop = getPropertyOfGeometry(); + if(!prop) return; + auto geo = prop->getComplexData(); + if(!geo) return; + bool reset = false; + auto version = getElementMapVersion(prop); + if(_ElementMapVersion.getStrValue().empty()) + _ElementMapVersion.setValue(version); + else if(_ElementMapVersion.getStrValue()!=version) { + reset = true; + _ElementMapVersion.setValue(version); + } + PropertyLinkBase::updateElementReferences(this,reset); +} + +void GeoFeature::onChanged(const Property *prop) { + if(prop==getPropertyOfGeometry()) { + if(getDocument() && !getDocument()->testStatus(Document::Restoring) + && !getDocument()->isPerformingTransaction()) + { + updateElementReference(); + } + } + DocumentObject::onChanged(prop); +} + +void GeoFeature::onDocumentRestored() { + if(!getDocument()->testStatus(Document::Status::Importing)) + _ElementMapVersion.setValue(getElementMapVersion(getPropertyOfGeometry(),true)); + DocumentObject::onDocumentRestored(); +} + +const std::vector& +GeoFeature::searchElementCache(const std::string &element, + bool checkGeometry, + double tol, + double atol) const +{ + static std::vector none; + (void)element; + (void)checkGeometry; + (void)tol; + (void)atol; + return none; +} + +const std::vector& +GeoFeature::getElementTypes(bool /*all*/) const +{ + static std::vector nil; + auto prop = getPropertyOfGeometry(); + if (!prop) + return nil; + return prop->getComplexData()->getElementTypes(); +} + +std::vector +GeoFeature::getHigherElements(const char *element, bool silent) const +{ + auto prop = getPropertyOfGeometry(); + if (!prop) + return {}; + return prop->getComplexData()->getHigherElements(element, silent); +} diff --git a/src/App/GeoFeature.h b/src/App/GeoFeature.h index c9d79dda58..3a26135644 100644 --- a/src/App/GeoFeature.h +++ b/src/App/GeoFeature.h @@ -24,7 +24,9 @@ #ifndef APP_GEOFEATURE_H #define APP_GEOFEATURE_H +#include #include "DocumentObject.h" +#include "MappedElement.h" #include "PropertyGeo.h" @@ -40,6 +42,7 @@ class AppExport GeoFeature : public App::DocumentObject public: PropertyPlacement Placement; + PropertyString _ElementMapVersion; /// Constructor GeoFeature(void); @@ -80,10 +83,17 @@ public: * @param name: input name * @param type: desired element name type to return * - * @return a pair(newName,oldName). New element name may be empty. + * This function relies on ComplexGeoData::elementMapPrefix() to decide + * whether it is a forward query, i.e. mapped -> original, or reverse + * query. The reason being that, unlike ComplexGeoData who deals with the + * actual element map data, GeoFeature here sits at a higher level. + * GeoFeature should be dealing with whatever various PropertyLinkSub(s) is + * assigned. * - * This function currently is does nothing. The new style element name - * generation will be added in the next batch of patches. + * This function is made virtual, so that inherited class can do something + * unusual, such as Sketcher::SketcherObject, which uses this to expose its + * private geometries without a correpsonding TopoShape, and yet being + * source code compatible. */ virtual std::pair getElementName( const char *name, ElementNameType type=Normal) const; @@ -107,6 +117,8 @@ public: bool append=false, ElementNameType type=Normal, const DocumentObject *filter=nullptr,const char **element=nullptr, GeoFeature **geo=nullptr); + static bool hasMissingElement(const char *subname); + /** * @brief Calculates the placement in the global reference coordinate system * @@ -120,6 +132,46 @@ public: * @return Base::Placement The transformation from the global reference coordinate system */ Base::Placement globalPlacement() const; + + /** Search sub element using internal cached geometry + * + * @param element: element name + * @param checkGeometry: search element by comparing geometry + * @param tol: coordinate tolerance + * @param atol: angle tolerance + * + * @return Returns a list of found element reference to the new goemetry. + * The returned value will be invalidated when the geometry is changed. + * + * Before changing the property of geometry, GeoFeature will internally + * make a snapshot of all referenced element geometry. After change, user + * code may call this function to search for the new element name that + * reference to the same geometry of the old element. + */ + virtual const std::vector& searchElementCache(const std::string &element, + bool checkGeometry = true, + double tol = 1e-7, + double atol = 1e-10) const; + + + /// Return the object that owns the shape that contains the give element name + virtual DocumentObject *getElementOwner(const Data::MappedName & /*name*/) const + {return nullptr;} + + virtual const std::vector& getElementTypes(bool all=true) const; + + /// Return the higher level element names of the given element + virtual std::vector getHigherElements(const char *name, bool silent=false) const; + +protected: + virtual void onChanged(const Property* prop); + virtual void onDocumentRestored(); + void updateElementReference(); + std::pair _getElementName(const char *name, const Data::MappedElement &mapped) const; + +private: + std::vector _elementMapCache; + std::string _elementMapVersion; }; } //namespace App diff --git a/src/App/MappedElement.cpp b/src/App/MappedElement.cpp new file mode 100644 index 0000000000..35f20e0600 --- /dev/null +++ b/src/App/MappedElement.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +#endif + +#include +#include +#include +#include "DocumentObject.h" +#include "MappedElement.h" + +using namespace Data; + +struct ByteArray +{ + ByteArray(const QByteArray &b) + :bytes(b) + {} + + ByteArray(const ByteArray &other) + :bytes(other.bytes) + {} + + ByteArray(ByteArray &&other) + :bytes(std::move(other.bytes)) + {} + + void mutate() const + { + QByteArray copy; + copy.append(bytes.constData(), bytes.size()); + bytes = copy; + } + + bool operator==(const ByteArray & other) const { + return bytes == other.bytes; + } + + mutable QByteArray bytes; +}; + +struct ByteArrayHasher +{ + std::size_t operator()(const ByteArray &bytes) const + { + return qHash(bytes.bytes); + } + + std::size_t operator()(const QByteArray &bytes) const + { + return qHash(bytes); + } +}; + +void IndexedName::set(const char *name, + int len, + const std::vector &types, + bool allowOthers) +{ + static std::unordered_set NameSet; + + if (len < 0) + len = static_cast(std::strlen(name)); + int i; + for (i=len-1; i>=0; --i) { + if (name[i]<'0' || name[i]>'9') + break; + } + ++i; + this->index = std::atoi(name+i); + + for (int j=0; j= 'a' && name[j] <= 'z' ) + || (name[j] >= 'A' && name[j] <= 'Z')) + continue; + this->type = ""; + return; + } + + for (const char * type : types) { + int j=0; + for (const char *n=name, *t=type; *n; ++n) { + if (*n != *t || j >= i) + break; + ++i; + ++t; + if (!*t) { + this->type = type; + return; + } + } + } + + if (allowOthers) { + auto res = NameSet.insert(QByteArray::fromRawData(name, i)); + if (res.second) + res.first->mutate(); + this->type = res.first->bytes.constData(); + } else + this->type = ""; +} + +void MappedName::compact() const +{ + auto self = const_cast(this); + + if (this->raw) { + self->data = QByteArray(self->data.constData(), self->data.size()); + self->raw = false; + } + +#if 0 + static std::unordered_set PostfixSet; + if (this->postfix.size()) { + auto res = PostfixSet.insert(this->postfix); + if (!res.second) + self->postfix = *res.first; + } +#endif +} + +bool ElementNameComp::operator()(const MappedName &a, const MappedName &b) const { + size_t size = std::min(a.size(),b.size()); + if(!size) + return a.size()bc) + res = 1; + } + }else if(std::isxdigit(ac)) + return false; + else + break; + } + if(res < 0) + return true; + else if(res > 0) + return false; + + for (; i bc) + return false; + } + return a.size()bc) + return false; + } else if(!std::isdigit(ac)) { + return false; + } else + break; + } + + // Then compare the following digits part by integer value + int res = 0; + for(;ibc) + res = 1; + } + }else if(std::isdigit(ac)) + return false; + else + break; + } + if(res < 0) + return true; + else if(res > 0) + return false; + + // Finally, compare the remaining tail using lexical order + for (; i bc) + return false; + } + return a.size()getID(); +} + +const std::string & MappedChildElements::prefix() +{ + static std::string _prefix(ComplexGeoData::elementMapPrefix() + ":R"); + return _prefix; +} diff --git a/src/App/MappedElement.h b/src/App/MappedElement.h new file mode 100755 index 0000000000..4b7fc09393 --- /dev/null +++ b/src/App/MappedElement.h @@ -0,0 +1,847 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + + +#ifndef _AppMappedElement_h_ +#define _AppMappedElement_h_ + +#include +#include +#include +#include +#include +#include +#include "ComplexGeoData.h" +#include "StringHasher.h" + +namespace App +{ +class DocumentObject; +} + +namespace Data +{ + +class AppExport IndexedName { +public: + explicit IndexedName(const char *name = nullptr, int _index = 0) + : index(0) + { + if (!name) + this->type = ""; + else { + set(name); + if (_index) + this->index = _index; + } + } + + IndexedName(const char *name, + const std::vector & types, + bool allowOthers=true) + { + set(name, -1, types, allowOthers); + } + + explicit IndexedName(const QByteArray & data) + { + set(data.constData(), data.size()); + } + + IndexedName(const IndexedName &other) + : type(other.type), index(other.index) + {} + + static IndexedName fromConst(const char *name, int index) { + IndexedName res; + res.type = name; + res.index = index; + return res; + } + + IndexedName & operator=(const IndexedName & other) + { + this->index = other.index; + this->type = other.type; + return *this; + } + + friend std::ostream & operator<<(std::ostream & s, const IndexedName & e) + { + s << e.type; + if (e.index > 0) + s << e.index; + return s; + } + + bool operator==(const IndexedName & other) const + { + return this->index == other.index + && (this->type == other.type + || std::strcmp(this->type, other.type)==0); + } + + IndexedName & operator+=(int offset) + { + this->index += offset; + assert(this->index >= 0); + return *this; + } + + IndexedName & operator++() + { + ++this->index; + return *this; + } + + IndexedName & operator--() + { + --this->index; + assert(this->index >= 0); + return *this; + } + + bool operator!=(const IndexedName & other) const + { + return !(this->operator==(other)); + } + + const char * toString(std::string & s) const + { + // Note! s is not cleared on purpose. + std::size_t offset = s.size(); + s += this->type; + if (this->index > 0) + s += std::to_string(this->index); + return s.c_str() + offset; + } + + int compare(const IndexedName & other) const + { + int res = std::strcmp(this->type, other.type); + if (res) + return res; + if (this->index < other.index) + return -1; + if (this->index > other.index) + return 1; + return 0; + } + + bool operator<(const IndexedName & other) const + { + return compare(other) < 0; + } + + char operator[](int index) const + { + return this->type[index]; + } + + const char * getType() const { return this->type; } + + int getIndex() const { return this->index; } + + void setIndex(int index) { assert(index>=0); this->index = index; } + + bool isNull() const { return !this->type[0]; } + + explicit operator bool() const { return !isNull(); } + +protected: + void set(const char *, + int len = -1, + const std::vector &types = {}, + bool allowOthers = true); + +private: + const char * type; + int index; +}; + +class AppExport MappedName +{ +public: + MappedName() + :raw(false) + {} + +#if QT_VERSION >= 0x050200 + MappedName(MappedName &&other) + :data(std::move(other.data)) + ,postfix(std::move(other.postfix)) + ,raw(other.raw) + {} + + MappedName & operator=(MappedName &&other) + { + this->data = std::move(other.data); + this->postfix = std::move(other.postfix); + this->raw = other.raw; + return *this; + } +#endif + + explicit MappedName(const char * name, int size = -1) + :raw(false) + { + if (!name) return; + if (boost::starts_with(name, ComplexGeoData::elementMapPrefix())) + name += ComplexGeoData::elementMapPrefix().size(); + if (size < 0) + data = QByteArray(name); + else + data = QByteArray(name, size); + } + + explicit MappedName(const std::string & name) + { + int size = name.size(); + const char *n = name.c_str(); + if (boost::starts_with(name, ComplexGeoData::elementMapPrefix())) { + n += ComplexGeoData::elementMapPrefix().size(); + size -= ComplexGeoData::elementMapPrefix().size(); + } + data = QByteArray(n, size); + } + + explicit MappedName(const IndexedName & element) + :data(element.getType()), raw(false) + { + if (element.getIndex() > 0) + data += QByteArray::number(element.getIndex()); + } + + explicit MappedName(const App::StringIDRef & sid) + :raw(false) + { + sid.toBytes(this->data); + } + + MappedName(const MappedName & other) + :data(other.data), postfix(other.postfix), raw(other.raw) + {} + + MappedName(const MappedName & other, int from, int size = -1) + : raw(false) + { + append(other, from, size); + } + + MappedName(const MappedName & other, const char *postfix) + :data(other.data + other.postfix) + ,postfix(postfix) + ,raw(false) + {} + + static MappedName fromRawData(const char * name, int size = -1) + { + MappedName res; + if (name) { + res.data = QByteArray::fromRawData(name, + size>=0 ? size: qstrlen(name)); + res.raw = true; + } + return res; + } + + static MappedName fromRawData(const QByteArray & data) + { + return fromRawData(data.constData(), data.size()); + } + + static MappedName fromRawData(const MappedName &other, int from, int size = -1) + { + if (from < 0) + from = 0; + + if (from >= other.size()) + return MappedName(); + + if (from >= other.data.size()) + return MappedName(other, from, size); + + MappedName res; + res.raw = true; + if (size < 0) + size = other.size() - from; + + if (size < other.data.size()-from) + res.data = QByteArray::fromRawData( + other.data.constData()+from, size); + else { + res.data = QByteArray::fromRawData( + other.data.constData()+from, other.data.size()-from); + size -= other.data.size() - from; + if (size == other.postfix.size()) + res.postfix = other.postfix; + else if (size) + res.postfix.append(other.postfix.constData(), size); + } + return res; + } + + MappedName & operator=(const MappedName & other) + { + this->data = other.data; + this->postfix = other.postfix; + this->raw = other.raw; + return *this; + } + + MappedName & operator=(const std::string & other) + { + *this = MappedName(other); + return *this; + } + + MappedName & operator=(const char * other) + { + *this = MappedName(other); + return *this; + } + + friend std::ostream & operator<<(std::ostream & s, const MappedName & n) + { + s.write(n.data.constData(), n.data.size()); + s.write(n.postfix.constData(), n.postfix.size()); + return s; + } + + bool operator==(const MappedName & other) const + { + if (this->size() != other.size()) + return false; + if (this->data.size() == other.data.size()) + return this->data == other.data && this->postfix == other.postfix; + const auto &a = this->data.size() < other.data.size() ? *this : other; + const auto &b = this->data.size() < other.data.size() ? other: *this; + if (!b.data.startsWith(a.data)) + return false; + QByteArray tmp = QByteArray::fromRawData( + b.data.constData() + a.data.size(), + b.data.size() - a.data.size()); + if (!a.postfix.startsWith(tmp)) + return false; + tmp = QByteArray::fromRawData( + a.postfix.constData() + tmp.size(), + a.postfix.size() - tmp.size()); + return tmp == b.postfix; + } + + bool operator!=(const MappedName & other) const + { + return !(this->operator==(other)); + } + + MappedName operator+(const MappedName & other) const + { + MappedName res(*this); + res += other; + return res; + } + + MappedName operator+(const char * other) const + { + MappedName res(*this); + res += other; + return res; + } + + MappedName operator+(const std::string & other) const + { + MappedName res(*this); + res += other; + return res; + } + + MappedName operator+(const QByteArray & other) const + { + MappedName res(*this); + res += other; + return res; + } + + MappedName & operator+=(const char * other) + { + if (other && other[0]) + this->postfix.append(other, -1); + return *this; + } + + MappedName & operator+=(const std::string & other) + { + if (other.size()) { + this->postfix.reserve(this->postfix.size() + other.size()); + this->postfix.append(other.c_str(), other.size()); + } + return *this; + } + + MappedName & operator+=(const QByteArray & other) + { + this->postfix += other; + return *this; + } + + MappedName & operator+=(const MappedName & other) + { + append(other); + return *this; + } + + void append(const char * d, int size = -1) + { + if (d && size) { + if (size < 0) + size = qstrlen(d); + if (empty()) + this->data.append(d, size); + else + this->postfix.append(d, size); + } + } + + void append(const MappedName & other, int from = 0, int size = -1) + { + if (from < 0) + from = 0; + else if (from > other.size()) + return; + if (size < 0 || size + from > other.size()) + size = other.size() - from; + + int count = size; + if (from < other.data.size()) { + if (count > other.data.size() - from) + count = other.data.size() - from; + if (from == 0 && count == other.data.size() && this->empty()) { + this->data = other.data; + this->raw = other.raw; + } else + append(other.data.constData() + from, count); + from = 0; + size -= count; + } else + from -= other.data.size(); + if (size) { + if (from == 0 && size == other.postfix.size()) { + if (this->empty()) + this->data = other.postfix; + else if (this->postfix.isEmpty()) + this->postfix = other.postfix; + else + this->postfix += other.postfix; + } else + append(other.postfix.constData() + from, size); + } + } + + std::string toString(int from, int len=-1) const + { + std::string res; + return toString(res, from, len); + } + + const char * toString(std::string &s, int from=0, int len=-1) const + { + std::size_t offset = s.size(); + int count = this->size(); + if (from < 0) + from = 0; + else if (from >= count) + return s.c_str()+s.size(); + if (len < 0 || len > count - from) + len = count - from; + s.reserve(s.size() + len); + if (from < this->data.size()) { + count = this->data.size() - from; + if (len < count) + count = len; + s.append(this->data.constData()+from, count); + len -= count; + } + s.append(this->postfix.constData(), len); + return s.c_str() + offset; + } + + const char * toConstString(int offset, int &size) const + { + if (offset < 0) + offset = 0; + if (offset > this->data.size()) { + offset -= this->data.size(); + if (offset > this->postfix.size()) { + size = 0; + return ""; + } + size = this->postfix.size() - offset; + return this->postfix.constData() + offset; + } + size = this->data.size() - offset; + return this->data.constData() + offset; + } + + QByteArray toRawBytes(int offset=0, int size=-1) const + { + if (offset < 0) + offset = 0; + if (offset >= this->size()) + return QByteArray(); + if (size < 0 || size > this->size() - offset) + size = this->size() - offset; + if (offset >= this->data.size()) { + offset -= this->data.size(); + return QByteArray::fromRawData(this->postfix.constData()+offset, size); + } + if (size <= this->data.size() - offset) + return QByteArray::fromRawData(this->data.constData()+offset, size); + + QByteArray res(this->data.constData()+offset, this->data.size()-offset); + res.append(this->postfix.constData(), size - this->data.size() + offset); + return res; + } + + const QByteArray & dataBytes() const + { + return this->data; + } + + const QByteArray & postfixBytes() const + { + return this->postfix; + } + + const char * constPostfix() const + { + return this->postfix.constData(); + } + + // No constData() because 'data' is allow to contain raw data, which may + // not end with 0. +#if 0 + void char * constData() const + { + return this->data.constData(); + } +#endif + + QByteArray toBytes() const + { + if (this->postfix.isEmpty()) + return this->data; + if (this->data.isEmpty()) + return this->postfix; + return this->data + this->postfix; + } + + IndexedName toIndexedName() const + { + if (this->postfix.isEmpty()) + return IndexedName(this->data); + return IndexedName(); + } + + std::string toPrefixedString() const + { + std::string res; + toPrefixedString(res); + return res; + } + + const char *toPrefixedString(std::string &buf) const + { + if (!toIndexedName()) + buf += ComplexGeoData::elementMapPrefix(); + toString(buf); + return buf.c_str(); + } + + int compare(const MappedName &other) const + { + int asize = this->size(); + int bsize = other.size(); + for (int i=0, count=std::min(asize, bsize); ioperator[](i); + char b = other[i]; + if (a < b) + return -1; + if (a > b) + return 1; + } + if (asize < bsize) + return -1; + if (asize > bsize) + return 1; + return 0; + } + + bool operator<(const MappedName & other) const + { + return compare(other) < 0; + } + + char operator[](int index) const + { + if (index >= this->data.size()) + return this->postfix[index - this->data.size()]; + return this->data[index]; + } + + int size() const + { + return this->data.size() + this->postfix.size(); + } + + bool empty() const + { + return this->data.isEmpty() && this->postfix.isEmpty(); + } + + bool isRaw() const + { + return this->raw; + } + + MappedName copy() const + { + if (!this->raw) + return *this; + MappedName res; + res.data.append(this->data.constData(), this->data.size()); + res.postfix = this->postfix; + return res; + } + + void compact() const; + + explicit operator bool() const + { + return !empty(); + } + + void clear() + { + this->data.clear(); + this->postfix.clear(); + this->raw = false; + } + + int find(const char *d, int from = 0) const + { + if (!d) + return -1; + if (from < 0) + from = 0; + if (from < this->data.size()) { + int res = this->data.indexOf(d, from); + if (res >= 0) + return res; + from = 0; + } else + from -= this->data.size(); + int res = this->postfix.indexOf(d, from); + if (res < 0) + return res; + return res + this->data.size(); + } + + int find(const std::string &d, int from = 0) const + { + return find(d.c_str(), from); + } + + int rfind(const char *d, int from = -1) const + { + if (!d) + return -1; + if (from < 0 || from > this->postfix.size()) { + if (from > postfix.size()) + from -= postfix.size(); + int res = this->postfix.lastIndexOf(d, from); + if (res >= 0) + return res + this->data.size(); + from = -1; + } + return this->data.lastIndexOf(d, from); + } + + int rfind(const std::string &d, int from = -1) const + { + return rfind(d.c_str(), from); + } + + bool endsWith(const char *s) const + { + if (!s) + return false; + if (this->postfix.size()) + return this->postfix.endsWith(s); + return this->data.endsWith(s); + } + + bool endsWith(const std::string &s) const + { + return endsWith(s.c_str()); + } + + bool startsWith(const QByteArray & s, int offset = 0) const + { + if (s.size() > size() - offset) + return false; + if (offset || (this->data.size() && this->data.size() < s.size())) + return toRawBytes(offset, s.size()) == s; + if (this->data.size()) + return this->data.startsWith(s); + return this->postfix.startsWith(s); + } + + bool startsWith(const char *s, int offset = 0) const + { + if (!s) + return false; + return startsWith(QByteArray::fromRawData(s, qstrlen(s)), offset); + } + + bool startsWith(const std::string &s, int offset = 0) const + { + return startsWith(QByteArray::fromRawData(s.c_str(), s.size()), offset); + } + + std::size_t hash() const + { +#if QT_VERSION >= 0x050000 + return qHash(data, qHash(postfix)); +#else + return qHash(data) ^ qHash(postfix); +#endif + } + +private: + QByteArray data; + QByteArray postfix; + bool raw; +}; + +struct AppExport MappedElement +{ + IndexedName index; + MappedName name; + + MappedElement() + {} + + MappedElement(const IndexedName & idx, const MappedName & n) + : index(idx), name(n) + {} + + MappedElement(const MappedName & n, const IndexedName & idx) + : index(idx), name(n) + {} + + MappedElement(const MappedElement & other) + : index(other.index), name(other.name) + {} + + MappedElement(MappedElement && other) + : index(std::move(other.index)), name(std::move(other.name)) + {} + + MappedElement & operator=(MappedElement && other) + { + this->index = std::move(other.index); + this->name = std::move(other.name); + return *this; + } + + MappedElement & operator=(const MappedElement & other) + { + this->index = other.index; + this->name = other.name; + return *this; + } + + bool operator==(const MappedElement &other) const + { + return this->index == other.index && this->name == other.name; + } + + bool operator!=(const MappedElement &other) const + { + return this->index != other.index || this->name != other.name; + } + + bool operator<(const MappedElement &other) const + { + int res = this->index.compare(other.index); + if (res < 0) + return true; + if (res > 0) + return false; + return this->name < other.name; + } +}; + +struct AppExport HistoryItem { + App::DocumentObject *obj; + long tag; + Data::MappedName element; + Data::IndexedName index; + std::vector intermediates; + HistoryItem(App::DocumentObject *obj, const Data::MappedName &name); +}; + +struct AppExport ElementNameComp { + /** Comparison function to make topo name more stable + * + * The sorting decompose 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 name with bigger digits (usually means + * comes late in history) comes early when sorting. + */ + bool operator()(const MappedName &a, const MappedName &b) const; +}; + +typedef QVector ElementIDRefs; + +struct AppExport MappedChildElements +{ + IndexedName indexedName; + int count; + int offset; + long tag; + ElementMapPtr elementMap; + QByteArray postfix; + ElementIDRefs sids; + + static const std::string & prefix(); +}; + +} //namespace Data + + +#endif diff --git a/src/App/Property.cpp b/src/App/Property.cpp index 7f6a4e3a46..c49b02938b 100644 --- a/src/App/Property.cpp +++ b/src/App/Property.cpp @@ -34,6 +34,7 @@ #include #include "Application.h" +#include "Document.h" #include "DocumentObject.h" #include "Property.h" #include "ObjectIdentifier.h" @@ -80,6 +81,12 @@ bool Property::isValidName(const char* name) return name && name[0] != '\0'; } +void Property::SetRestoreError(const char * msg) +{ + if (auto doc = father->getOwnerDocument()) + doc->setErrorDescription(this, msg); +} + std::string Property::getFullName(bool python) const { if(!myName || (python && !father)) return std::string(python?"None":"?"); diff --git a/src/App/Property.h b/src/App/Property.h index 768c2cd521..ede579333f 100644 --- a/src/App/Property.h +++ b/src/App/Property.h @@ -254,6 +254,8 @@ public: /// Compare if this property has the same content as the given one virtual bool isSame(const Property &other) const; + virtual void SetRestoreError(const char *) override; + /** Return a unique ID for the property * * The ID of a property is generated from a monotonically increasing diff --git a/src/App/PropertyGeo.cpp b/src/App/PropertyGeo.cpp index aa8ebe810c..14747f6902 100644 --- a/src/App/PropertyGeo.cpp +++ b/src/App/PropertyGeo.cpp @@ -23,19 +23,30 @@ #include "PreCompiled.h" -#include -#include -#include +#ifndef _PreComp_ +# include +#endif +#include + +/// Here the FreeCAD includes sorted by Base,App,Gui...... + +#include +#include +#include +#include #include #include #include #include #include #include +#include #include #include +#include "Document.h" +#include "DocumentObject.h" #include "PropertyGeo.h" #include "Placement.h" #include "ObjectIdentifier.h" @@ -1136,3 +1147,48 @@ PropertyComplexGeoData::~PropertyComplexGeoData() { } + +std::string PropertyComplexGeoData::getElementMapVersion(bool) const { + auto data = getComplexData(); + if(!data) + return std::string(); + auto owner = Base::freecad_dynamic_cast(getContainer()); + std::ostringstream ss; + if(owner && owner->getDocument() + && owner->getDocument()->getStringHasher()==data->Hasher) + ss << "1."; + else + ss << "0."; + ss << data->getElementMapVersion(); + return ss.str(); +} + +bool PropertyComplexGeoData::checkElementMapVersion(const char * ver) const +{ + auto data = getComplexData(); + if(!data) + return false; + auto owner = Base::freecad_dynamic_cast(getContainer()); + std::ostringstream ss; + const char *prefix; + if(owner && owner->getDocument() + && owner->getDocument()->getStringHasher() == data->Hasher) + prefix = "1."; + else + prefix = "0."; + if (!boost::starts_with(ver, prefix)) + return true; + return data->checkElementMapVersion(ver+2); +} + +void PropertyComplexGeoData::afterRestore() +{ + auto data = getComplexData(); + if (data && data->isRestoreFailed()) { + data->resetRestoreFailure(); + auto owner = Base::freecad_dynamic_cast(getContainer()); + if (owner && owner->getDocument() && !owner->getDocument()->testStatus(App::Document::PartialDoc)) + owner->getDocument()->addRecomputeObject(owner); + } + PropertyGeometry::afterRestore(); +} diff --git a/src/App/PropertyGeo.h b/src/App/PropertyGeo.h index 1515809be7..89c4e6060a 100644 --- a/src/App/PropertyGeo.h +++ b/src/App/PropertyGeo.h @@ -541,6 +541,18 @@ public: virtual const Data::ComplexGeoData* getComplexData() const = 0; virtual Base::BoundBox3d getBoundingBox() const = 0; //@} + + /** Return the element map version + * + * @param persisted: if true, return the restored element map version. Or + * else, return the current element map version + */ + virtual std::string getElementMapVersion(bool restored=false) const; + + /// Return true to signal element map version change + virtual bool checkElementMapVersion(const char * ver) const; + + virtual void afterRestore(); }; } // namespace App diff --git a/src/App/PropertyLinks.cpp b/src/App/PropertyLinks.cpp index 4616ab12bc..10a69e85cd 100644 --- a/src/App/PropertyLinks.cpp +++ b/src/App/PropertyLinks.cpp @@ -26,21 +26,28 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include +#include #include "PropertyLinks.h" #include "Application.h" #include "Document.h" #include "DocumentObject.h" #include "DocumentObjectPy.h" +#include "DocumentObserver.h" +#include "GeoFeature.h" #include "ObjectIdentifier.h" -FC_LOG_LEVEL_INIT("PropertyLinks",true,true) +FC_LOG_LEVEL_INIT("App",true,true) using namespace App; using namespace Base; @@ -55,6 +62,9 @@ namespace bp = boost::placeholders; TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkBase , App::Property) static std::unordered_map > _LabelMap; + +static std::unordered_map > _ElementRefMap; + PropertyLinkBase::PropertyLinkBase() {} @@ -67,6 +77,11 @@ void PropertyLinkBase::setAllowExternal(bool allow) { setFlag(LinkAllowExternal,allow); } +void PropertyLinkBase::setReturnNewElement(bool enable) +{ + setFlag(LinkNewElement, enable); +} + void PropertyLinkBase::hasSetValue() { auto owner = dynamic_cast(getContainer()); if(owner) @@ -98,6 +113,15 @@ bool PropertyLinkBase::isSame(const Property &other) const } void PropertyLinkBase::unregisterElementReference() { + for(auto obj : _ElementRefs) { + auto it = _ElementRefMap.find(obj); + if(it != _ElementRefMap.end()) { + it->second.erase(this); + if(it->second.empty()) + _ElementRefMap.erase(it); + } + } + _ElementRefs.clear(); } void PropertyLinkBase::unregisterLabelReferences() @@ -202,39 +226,76 @@ static std::string propertyName(const Property *prop) { return prop->getFullName(); } +std::vector +PropertyLinkBase::linkedElementsT(bool all) const +{ + std::vector objs; + std::vector subs; + getLinks(objs,all,&subs,true); + std::vector res; + res.reserve(objs.size()); + assert(objs.size() == subs.size()); + for (unsigned i=0; i& +PropertyLinkBase::getElementReferences(DocumentObject *feature) +{ + static std::unordered_set none; + + auto it = _ElementRefMap.find(feature); + if(it == _ElementRefMap.end()) + return none; + + return it->second; +} + void PropertyLinkBase::updateElementReferences(DocumentObject *feature, bool reverse) { - (void)feature; - (void)reverse; + if(!feature || !feature->getNameInDocument()) + return; + auto it = _ElementRefMap.find(feature); + if(it == _ElementRefMap.end()) + return; + std::vector props; + props.reserve(it->second.size()); + props.insert(props.end(),it->second.begin(),it->second.end()); + for(auto prop : props) { + if(prop->getContainer()) { + try { + prop->updateElementReference(feature,reverse,true); + }catch(Base::Exception &e) { + e.ReportException(); + FC_ERR("Failed to update element reference of " << propertyName(prop)); + }catch(std::exception &e) { + FC_ERR("Failed to update element reference of " << propertyName(prop) + << ": " << e.what()); + } + } + } } void PropertyLinkBase::_registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow) { - (void)obj; - (void)sub; - (void)shadow; + if(!obj || !obj->getNameInDocument() || sub.empty()) + return; + if(shadow.first.empty()) { + _updateElementReference(0,obj,sub,shadow,false); + return; + } + GeoFeature *geo = 0; + const char *element = 0; + std::pair elementName; + GeoFeature::resolveElement(obj,sub.c_str(), elementName,true, + GeoFeature::ElementNameType::Export,0,&element,&geo); + if(!geo || !element || !element[0]) + return; + + if(_ElementRefs.insert(geo).second) + _ElementRefMap[geo].insert(this); } -class StringGuard { -public: - StringGuard(char *c) - :c(c) - { - v1 = c[0]; - v2 = c[1]; - c[0] = '.'; - c[1] = 0; - } - ~StringGuard() - { - c[0] = v1; - c[1] = v2; - } - - char *c; - char v1; - char v2; -}; - void PropertyLinkBase::restoreLabelReference(const DocumentObject *obj, std::string &subname, ShadowSub *shadow) { @@ -276,12 +337,136 @@ bool PropertyLinkBase::_updateElementReference(DocumentObject *feature, App::DocumentObject *obj, std::string &sub, ShadowSub &shadow, bool reverse, bool notify) { - (void)feature; - (void)obj; - (void)reverse; - (void)notify; - shadow.second = sub; - return false; + if(!obj || !obj->getNameInDocument()) return false; + ShadowSub elementName; + const char *subname; + if(shadow.first.size()) + subname = shadow.first.c_str(); + else if(shadow.second.size()) + subname = shadow.second.c_str(); + else + subname = sub.c_str(); + GeoFeature *geo = 0; + const char *element=0; + auto ret = GeoFeature::resolveElement(obj,subname, elementName,true, + GeoFeature::ElementNameType::Export,feature,&element,&geo); + if(!ret || !geo || !element || !element[0]) { + if(elementName.second.size()) + shadow.second.swap(elementName.second); + return false; + } + + if(_ElementRefs.insert(geo).second) + _ElementRefMap[geo].insert(this); + + if (!reverse) { + if (elementName.first.empty()) { + shadow.second.swap(elementName.second); + return false; + } + if(shadow==elementName) + return false; + } + + bool missing = GeoFeature::hasMissingElement(elementName.second.c_str()); + if (feature == geo && (missing || reverse)) { + // If the referenced element is missing, or we are generating element + // map for the first time, or we are re-generating the element map due + // to version change, i.e. 'reverse', try search by geometry first + const char *oldElement = Data::ComplexGeoData::findElementName(shadow.second.c_str()); + if(!Data::ComplexGeoData::hasMissingElement(oldElement)) { + const auto &names = geo->searchElementCache(oldElement); + if(names.size()) { + missing = false; + std::string newsub(subname, strlen(subname) - strlen(element)); + newsub += names.front(); + GeoFeature::resolveElement(obj, newsub.c_str(), elementName,true, + GeoFeature::ElementNameType::Export,feature); + FC_WARN(propertyName(this) + << " auto change element reference " << ret->getFullName() << " " + << (shadow.first.size()?shadow.first:shadow.second) << " -> " + << (elementName.first.size()?elementName.first:elementName.second)); + } + // Note: the following code proves to be too risky. There is no way + // (so far) to ensure the recompute do not change the geometry. If + // the geometry does remain the same, the above geometry search + // should be able to find the new reference any way! +#if 0 + else if (missing && reverse && shadow.first.size()) { + // reverse means we are trying to either generate the element + // name for the first time, or upgrade to a new map version. In + // case of upgrading, we still consult the original mapped name + // in first try. Here means the first try failed, and the + // geometry search cannot find any match, so we try the + // non-mapped name as a last resort. + // + // WARNING! We are assuming the recomputation is done with no + // actual property change, and the resulting geometry remains + // the same. If this condition is not met, the result may be + // undesirable. TODO: find a way to ensure this condition. + + GeoFeature::resolveElement(obj, shadow.second.c_str(), elementName, true, + GeoFeature::ElementNameType::Export,feature); + if(!elementName.second.empty()) { + missing = Data::ComplexGeoData::hasMissingElement(elementName.second.c_str()); + if (!missing) { + FC_WARN(propertyName(this) + << " element reference changed " << ret->getFullName() << " " + << shadow.first << " -> " << elementName.first); + } + } + } +#endif + } + } + + if(notify) + aboutToSetValue(); + if(missing) { + FC_WARN(propertyName(this) + << " missing element reference " << ret->getFullName() << " " + << (elementName.first.size()?elementName.first:elementName.second)); + shadow.second.swap(elementName.second); + } else { + FC_TRACE(propertyName(this) + << " element reference shadow update " << ret->getFullName() << " " + << shadow.first << " -> " << elementName.first); + shadow.swap(elementName); + if(shadow.first.size() && Data::ComplexGeoData::hasMappedElementName(sub.c_str())) + sub = shadow.first; + } + + if(reverse) { + if(shadow.first.size() && Data::ComplexGeoData::hasMappedElementName(sub.c_str())) + sub = shadow.first; + else + sub = shadow.second; + return true; + } + if (missing) { + if (sub != shadow.first) + sub = shadow.second; + return true; + } + auto pos2 = shadow.first.rfind('.'); + if(pos2 == std::string::npos) + return true; + ++pos2; + auto pos = sub.rfind('.'); + if(pos == std::string::npos) + pos = 0; + else + ++pos; + if(pos==pos2) { + if(sub.compare(pos,sub.size()-pos,&shadow.first[pos2])!=0) { + FC_LOG("element reference update " << sub << " -> " << shadow.first); + sub.replace(pos,sub.size()-pos,&shadow.first[pos2]); + } + } else if(sub!=shadow.second) { + FC_LOG("element reference update " << sub << " -> " << shadow.second); + sub = shadow.second; + } + return true; } std::pair @@ -290,6 +475,8 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject { std::pair res; res.first = 0; + if (!obj) + return res; if(oldObj == obj) { if(owner == parent) { @@ -309,6 +496,11 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject return res; } return res; + } else if (newObj == obj) { + // This means the new object is already sub-object of this parent + // (consider a case of swapping the tool and base object of the Cut + // feature). We'll swap the old and new object. + return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname); } if(!subname || !subname[0]) return res; @@ -319,6 +511,8 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos)) { ++pos; char c = sub[pos]; + if (c == '.') + continue; sub[pos] = 0; auto sobj = obj->getSubObject(sub.c_str()); sub[pos] = c; @@ -343,6 +537,8 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject return res; } break; + }else if(sobj == newObj) { + return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname); }else if(prev == parent) break; prev = sobj; @@ -358,6 +554,8 @@ PropertyLinkBase::tryReplaceLinkSubs(const PropertyContainer *owner, { std::pair > res; res.first = 0; + if (!obj) + return res; auto r = tryReplaceLink(owner,obj,parent,oldObj,newObj); if(r.first) { @@ -917,7 +1115,7 @@ TYPESYSTEM_SOURCE(App::PropertyLinkSubHidden, App::PropertyLinkSub) PropertyLinkSub::PropertyLinkSub() - : _pcLinkSub(nullptr), _restoreLabel(false) + : _pcLinkSub(nullptr) { } @@ -993,13 +1191,22 @@ const std::vector& PropertyLinkSub::getSubValues(void) const } static inline const std::string &getSubNameWithStyle(const std::string &subName, - const PropertyLinkBase::ShadowSub &shadow, bool newStyle) + const PropertyLinkBase::ShadowSub &shadow, bool newStyle, std::string &tmp) { if(!newStyle) { if(shadow.second.size()) return shadow.second; - }else if(shadow.first.size()) + }else if(shadow.first.size()) { + if (Data::ComplexGeoData::hasMissingElement(shadow.second.c_str())) { + auto pos = shadow.first.rfind('.'); + if (pos != std::string::npos) { + tmp = shadow.first.substr(0, pos+1); + tmp += shadow.second; + return tmp; + } + } return shadow.first; + } return subName; } @@ -1007,20 +1214,24 @@ std::vector PropertyLinkSub::getSubValues(bool newStyle) const { assert(_cSubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_cSubList.size()); + std::string tmp; for(size_t i=0;i<_ShadowSubList.size();++i) - ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle)); + ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp)); return ret; } std::vector PropertyLinkSub::getSubValuesStartsWith(const char* starter, bool newStyle) const { - (void)newStyle; - - std::vector temp; - for(std::vector::const_iterator it=_cSubList.begin();it!=_cSubList.end();++it) - if(strncmp(starter,it->c_str(),strlen(starter))==0) - temp.push_back(*it); - return temp; + assert(_cSubList.size() == _ShadowSubList.size()); + std::vector ret; + std::string tmp; + for(size_t i=0;i<_ShadowSubList.size();++i) { + const auto &sub = getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp); + auto element = Data::ComplexGeoData::findElementName(sub.c_str()); + if(element && boost::starts_with(element,starter)) + ret.emplace_back(element); + } + return ret; } App::DocumentObject * PropertyLinkSub::getValue(Base::Type t) const @@ -1035,8 +1246,9 @@ PyObject *PropertyLinkSub::getPyObject(void) if (_pcLinkSub) { _pcLinkSub->getPyObject(); tup[0] = Py::asObject(_pcLinkSub->getPyObject()); - for(unsigned int i = 0;i<_cSubList.size(); i++) - list[i] = Py::String(_cSubList[i]); + int i = 0; + for (auto &sub : getSubValues(testFlag(LinkNewElement))) + list[i++] = Py::String(sub); tup[1] = list; return Py::new_reference_to(tup); } @@ -1309,12 +1521,9 @@ std::string PropertyLinkBase::tryImportSubName(const App::DocumentObject *obj, c return std::string(); } -#define ATTR_SHADOWED "shadowed" -#define ATTR_SHADOW "shadow" -#define ATTR_MAPPED "mapped" - -// We do not have topo naming yet, ignore shadow sub for now -#define IGNORE_SHADOW true +static const char *AttrShadowed = "shadowed"; +static const char *AttrShadow = "shadow"; +static const char *AttrMapped = "mapped"; void PropertyLinkSub::Save (Base::Writer &writer) const { @@ -1327,7 +1536,7 @@ void PropertyLinkSub::Save (Base::Writer &writer) const internal_name = _pcLinkSub->getExportName(); writer.Stream() << writer.ind() << "" << std::endl; + writer.Stream() << "\">\n"; writer.incInd(); auto owner = dynamic_cast(getContainer()); bool exporting = owner && owner->isExporting(); @@ -1342,18 +1551,18 @@ void PropertyLinkSub::Save (Base::Writer &writer) const std::string exportName; writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLinkSub,sub.c_str())); if(shadow.second.size() && shadow.first == _cSubList[i]) - writer.Stream() << "\" " ATTR_MAPPED "=\"1"; + writer.Stream() << "\" " << AttrMapped << "=\"1"; } else { writer.Stream() << encodeAttribute(sub); if(_cSubList[i].size()) { if(sub!=_cSubList[i]) { // Stores the actual value that is shadowed. For new version FC, // we will restore this shadowed value instead. - writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_cSubList[i]); + writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(_cSubList[i]); }else if(shadow.first.size()){ // Here means the user set value is old style element name. // We shall then store the shadow somewhere else. - writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first); + writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadow.first); } } } @@ -1394,15 +1603,15 @@ void PropertyLinkSub::Restore(Base::XMLReader &reader) for (int i = 0; i < count; i++) { reader.readElement("Sub"); shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel); - if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { + if(reader.hasAttribute(AttrShadowed)) { values[i] = shadows[i].first = - importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel); } else { values[i] = shadows[i].second; - if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) - shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + if(reader.hasAttribute(AttrShadow)) + shadows[i].first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel); } - if(reader.hasAttribute(ATTR_MAPPED)) + if(reader.hasAttribute(AttrMapped)) mapped.push_back(i); } setFlag(LinkRestoreLabel,restoreLabel); @@ -1502,6 +1711,7 @@ Property *PropertyLinkSub::Copy(void) const PropertyLinkSub *p= new PropertyLinkSub(); p->_pcLinkSub = _pcLinkSub; p->_cSubList = _cSubList; + p->_ShadowSubList = _ShadowSubList; return p; } @@ -1999,7 +2209,7 @@ std::vector PropertyLinkSubList::getSubListValues(b PyObject *PropertyLinkSubList::getPyObject(void) { #if 1 - std::vector subLists = getSubListValues(); + std::vector subLists = getSubListValues(testFlag(LinkNewElement)); std::size_t count = subLists.size(); #if 0//FIXME: Should switch to tuple Py::Tuple sequence(count); @@ -2190,18 +2400,18 @@ void PropertyLinkSubList::Save (Base::Writer &writer) const std::string exportName; writer.Stream() << encodeAttribute(exportSubName(exportName,obj,sub.c_str())); if(shadow.second.size() && _lSubList[i]==shadow.first) - writer.Stream() << "\" " ATTR_MAPPED "=\"1"; + writer.Stream() << "\" " << AttrMapped << "=\"1"; } else { writer.Stream() << encodeAttribute(sub); if(_lSubList[i].size()) { if(sub!=_lSubList[i]) { // Stores the actual value that is shadowed. For new version FC, // we will restore this shadowed value instead. - writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_lSubList[i]); + writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(_lSubList[i]); }else if(shadow.first.size()) { // Here means the user set value is old style element name. // We shall then store the shadow somewhere else. - writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first); + writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadow.first); } } } @@ -2242,15 +2452,15 @@ void PropertyLinkSubList::Restore(Base::XMLReader &reader) shadows.emplace_back(); auto &shadow = shadows.back(); shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel); - if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { - shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + if(reader.hasAttribute(AttrShadowed)) { + shadow.first = importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel); SubNames.push_back(shadow.first); }else{ SubNames.push_back(shadow.second); - if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) - shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + if(reader.hasAttribute(AttrShadow)) + shadow.first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel); } - if(reader.hasAttribute(ATTR_MAPPED)) + if(reader.hasAttribute(AttrMapped)) mapped.push_back(i); } else if (reader.isVerbose()) Base::Console().Warning("Lost link to '%s' while loading, maybe " @@ -2261,7 +2471,7 @@ void PropertyLinkSubList::Restore(Base::XMLReader &reader) reader.readEndElement("LinkSubList"); // assignment - setValues(values,SubNames,std::move(shadows)); + setValues(values,std::move(SubNames),std::move(shadows)); _mapped.swap(mapped); } @@ -2450,6 +2660,7 @@ Property *PropertyLinkSubList::Copy(void) const PropertyLinkSubList *p = new PropertyLinkSubList(); p->_lValueList = _lValueList; p->_lSubList = _lSubList; + p->_ShadowSubList = _ShadowSubList; return p; } @@ -2473,8 +2684,9 @@ std::vector PropertyLinkSubList::getSubValues(bool newStyle) const assert(_lSubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_ShadowSubList.size()); + std::string tmp; for(size_t i=0;i<_ShadowSubList.size();++i) - ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle)); + ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle,tmp)); return ret; } @@ -2627,7 +2839,7 @@ public: App::Document *pDoc,PropertyXLink *l, const char *objName) { QString path; - l->filePath = getDocPath(filename,pDoc,true,&path); + l->filePath = getDocPath(filename,pDoc,false,&path); FC_LOG("finding doc " << filename); @@ -2978,6 +3190,7 @@ PropertyXLink::PropertyXLink(bool _allowPartial, PropertyLinkBase *parent) setAllowPartial(_allowPartial); setAllowExternal(true); setSyncSubObject(true); + // setReturnNewElement(true); if(parent) setContainer(parent->getContainer()); } @@ -3009,18 +3222,27 @@ void PropertyXLink::detach() { } } +std::string PropertyXLink::getFullName(bool python) const { + if(getName() || python || !parentProp) + return inherited::getFullName(python); + + std::ostringstream ss; + ss << parentProp->getFullName() << ":" << this; + return ss.str(); +} + void PropertyXLink::aboutToSetValue() { if(parentProp) parentProp->aboutToSetChildValue(*this); else - PropertyLinkBase::aboutToSetValue(); + inherited::aboutToSetValue(); } void PropertyXLink::hasSetValue() { if(parentProp) parentProp->hasSetChildValue(*this); else - PropertyLinkBase::hasSetValue(); + inherited::hasSetValue(); } void PropertyXLink::setSubName(const char *subname) @@ -3329,9 +3551,11 @@ void PropertyXLink::Save (Base::Writer &writer) const { }else FC_WARN("PropertyXLink export without saving the document"); } - if(_path.size()) - path = _path.c_str(); - } + } else if (filePath.size()) + _path = DocInfo::getDocPath(filePath.c_str(),owner->getDocument(),true); + + if(_path.size()) + path = _path.c_str(); writer.Stream() << writer.ind() << "pcDoc?docInfo->pcDoc->LastModifiedDate.getValue():"") @@ -3352,14 +3576,14 @@ void PropertyXLink::Save (Base::Writer &writer) const { writer.Stream() << "\" sub=\"" << encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str())); if(shadowSub.second.size() && shadowSub.first==subName) - writer.Stream() << "\" " ATTR_MAPPED "=\"1"; + writer.Stream() << "\" " << AttrMapped << "=\"1"; }else{ writer.Stream() << "\" sub=\"" << encodeAttribute(sub); if(sub.size()) { if(sub!=subName) - writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(subName); + writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(subName); else if(shadowSub.first.size()) - writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadowSub.first); + writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadowSub.first); } } writer.Stream() << "\"/>" << std::endl; @@ -3377,14 +3601,14 @@ void PropertyXLink::Save (Base::Writer &writer) const { std::string exportName; writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str())); if(shadow.second.size() && shadow.first == _SubList[i]) - writer.Stream() << "\" " ATTR_MAPPED "=\"1"; + writer.Stream() << "\" " << AttrMapped << "=\"1"; } else { writer.Stream() << encodeAttribute(sub); if(_SubList[i].size()) { if(sub!=_SubList[i]) - writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_SubList[i]); + writer.Stream() << "\" " << AttrShadowed << "=\"" << encodeAttribute(_SubList[i]); else if(shadow.first.size()) - writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first); + writer.Stream() << "\" " << AttrShadow << "=\"" << encodeAttribute(shadow.first); } } writer.Stream()<<"\"/>" << endl; @@ -3431,19 +3655,19 @@ void PropertyXLink::Restore(Base::XMLReader &reader) std::vector mapped; bool restoreLabel = false; if(reader.hasAttribute("sub")) { - if(reader.hasAttribute(ATTR_MAPPED)) + if(reader.hasAttribute(AttrMapped)) mapped.push_back(0); subs.emplace_back(); auto &subname = subs.back(); shadows.emplace_back(); auto &shadow = shadows.back(); shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel); - if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) - subname = shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + if(reader.hasAttribute(AttrShadowed)) + subname = shadow.first = importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel); else { subname = shadow.second; - if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) - shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + if(reader.hasAttribute(AttrShadow)) + shadow.first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel); } }else if(reader.hasAttribute("count")) { int count = reader.getAttributeAsInteger("count"); @@ -3452,15 +3676,15 @@ void PropertyXLink::Restore(Base::XMLReader &reader) for (int i = 0; i < count; i++) { reader.readElement("Sub"); shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel); - if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) - subs[i] = shadows[i].first = - importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + if(reader.hasAttribute(AttrShadowed)) + subs[i] = shadows[i].first = + importSubName(reader,reader.getAttribute(AttrShadowed),restoreLabel); else { subs[i] = shadows[i].second; - if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) - shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + if(reader.hasAttribute(AttrShadow)) + shadows[i].first = importSubName(reader,reader.getAttribute(AttrShadow),restoreLabel); } - if(reader.hasAttribute(ATTR_MAPPED)) + if(reader.hasAttribute(AttrMapped)) mapped.push_back(i); } reader.readEndElement("XLink"); @@ -3541,8 +3765,10 @@ void PropertyXLink::copyTo(PropertyXLink &other, } if(subs) other._SubList = std::move(*subs); - else + else { other._SubList = _SubList; + other._ShadowSubList = _ShadowSubList; + } other._Flags = _Flags; } @@ -3661,7 +3887,7 @@ PyObject *PropertyXLink::getPyObject(void) { if(!_pcLink) Py_Return; - const auto &subs = getSubValues(false); + const auto &subs = getSubValues(testFlag(LinkNewElement)); if(subs.empty()) return _pcLink->getPyObject(); Py::Tuple ret(2); @@ -3725,7 +3951,7 @@ void PropertyXLink::setPyObject(PyObject *value) { const char *PropertyXLink::getSubName(bool newStyle) const { if(_SubList.empty() || _ShadowSubList.empty()) return ""; - return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle).c_str(); + return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle,tmpShadow).c_str(); } void PropertyXLink::getLinks(std::vector &objs, @@ -3756,20 +3982,24 @@ std::vector PropertyXLink::getSubValues(bool newStyle) const { assert(_SubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_SubList.size()); + std::string tmp; for(size_t i=0;i<_ShadowSubList.size();++i) - ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle)); + ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle,tmp)); return ret; } std::vector PropertyXLink::getSubValuesStartsWith(const char* starter, bool newStyle) const { - (void)newStyle; - - std::vector temp; - for(std::vector::const_iterator it=_SubList.begin();it!=_SubList.end();++it) - if(strncmp(starter,it->c_str(),strlen(starter))==0) - temp.push_back(*it); - return temp; + assert(_SubList.size() == _ShadowSubList.size()); + std::vector ret; + std::string tmp; + for(size_t i=0;i<_ShadowSubList.size();++i) { + const auto &sub = getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle,tmp); + auto element = Data::ComplexGeoData::findElementName(sub.c_str()); + if(element && boost::starts_with(element,starter)) + ret.emplace_back(element); + } + return ret; } void PropertyXLink::setAllowPartial(bool enable) { @@ -3825,7 +4055,7 @@ PyObject *PropertyXLinkSub::getPyObject(void) Py_Return; Py::Tuple ret(2); ret.setItem(0,Py::Object(_pcLink->getPyObject(),true)); - const auto &subs = getSubValues(false); + const auto &subs = getSubValues(testFlag(LinkNewElement)); Py::List list(subs.size()); int i = 0; PropertyString propString; @@ -3851,6 +4081,7 @@ PropertyXLinkSubList::PropertyXLinkSubList() { _pcScope = LinkScope::Global; setSyncSubObject(true); + // setReturnNewElement(true); } PropertyXLinkSubList::~PropertyXLinkSubList() @@ -4086,7 +4317,7 @@ PyObject *PropertyXLinkSubList::getPyObject(void) Py::Tuple tup(2); tup[0] = Py::asObject(obj->getPyObject()); - const auto &subs = link.getSubValues(); + const auto &subs = link.getSubValues(testFlag(LinkNewElement)); Py::Tuple items(subs.size()); for (std::size_t j = 0; j < subs.size(); j++) { items[j] = Py::String(subs[j]); @@ -4106,7 +4337,7 @@ void PropertyXLinkSubList::setPyObject(PyObject *value) this->setValue(dummy.getValue(), dummy.getSubValues()); return; } - catch (Base::Exception&) {} + catch (Base::TypeError&) {} if (!PyTuple_Check(value) && !PyList_Check(value)) throw Base::TypeError("Invalid type. Accepts (DocumentObject, (subname...)) or sequence of such type."); diff --git a/src/App/PropertyLinks.h b/src/App/PropertyLinks.h index 08f508036c..374972a540 100644 --- a/src/App/PropertyLinks.h +++ b/src/App/PropertyLinks.h @@ -26,8 +26,11 @@ #include #include +#include #include #include +#include +#include #include "Property.h" namespace Base { @@ -38,6 +41,8 @@ namespace App { class DocumentObject; class Document; +class GeoFeature; +class SubObjectT; class DocInfo; typedef std::shared_ptr DocInfoPtr; @@ -254,7 +259,7 @@ public: /// Helper function to return linked objects using an std::inserter template - void getLinkedObjects(T &inserter, bool all=false) const { + void getLinkedObjects(T inserter, bool all=false) const { std::vector ret; getLinks(ret,all); std::copy(ret.begin(),ret.end(),inserter); @@ -262,7 +267,7 @@ public: /// Helper function to return a map of linked object and its subname references void getLinkedElements(std::map > &elements, - bool newStyle=true, bool all=true) const + bool newStyle=true, bool all=false) const { std::vector ret; std::vector subs; @@ -275,12 +280,14 @@ public: /// Helper function to return a map of linked object and its subname references std::map > - linkedElements(bool newStyle=true, bool all=true) const + linkedElements(bool newStyle=true, bool all=false) const { std::map > ret; getLinkedElements(ret,newStyle,all); return ret; } + + std::vector linkedElementsT(bool all) const; //@} virtual bool isSame(const Property &other) const override; @@ -346,6 +353,8 @@ public: /// Update all element references in all link properties of \a feature static void updateElementReferences(DocumentObject *feature, bool reverse=false); + /// Obtain link properties that contain element references to a given object + static const std::unordered_set& getElementReferences(DocumentObject *); /** Helper function for update individual element reference * @@ -538,6 +547,7 @@ public: LinkAllowPartial, LinkRestoreLabel, LinkSyncSubObject, // used by DlgPropertyLink + LinkNewElement, // return new element name in getPyObject }; inline bool testFlag(int flag) const { return _Flags.test((std::size_t)flag); @@ -545,6 +555,8 @@ public: virtual void setAllowPartial(bool enable) { (void)enable; } + void setReturnNewElement(bool enable); + protected: virtual void hasSetValue() override; @@ -1052,6 +1064,7 @@ class PropertyXLinkSubList; class AppExport PropertyXLink : public PropertyLinkGlobal { TYPESYSTEM_HEADER_WITH_OVERRIDE(); + typedef PropertyLinkGlobal inherited; public: PropertyXLink(bool allowPartial=false, PropertyLinkBase *parent=nullptr); @@ -1137,6 +1150,7 @@ public: virtual void setAllowPartial(bool enable) override; + virtual std::string getFullName(bool python=false) const override; const char *getFilePath() const { return filePath.c_str(); } @@ -1169,6 +1183,7 @@ protected: std::vector _ShadowSubList; std::vector _mapped; PropertyLinkBase *parentProp; + mutable std::string tmpShadow; }; @@ -1198,10 +1213,10 @@ class AppExport PropertyXLinkSubList: public PropertyLinkBase { TYPESYSTEM_HEADER_WITH_OVERRIDE(); +public: typedef typename AtomicPropertyChangeInterface::AtomicPropertyChange atomic_change; friend atomic_change; -public: PropertyXLinkSubList(); virtual ~PropertyXLinkSubList(); diff --git a/src/App/StringHasher.cpp b/src/App/StringHasher.cpp index 596c47bb97..388892abd7 100644 --- a/src/App/StringHasher.cpp +++ b/src/App/StringHasher.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include FC_LOG_LEVEL_INIT("App",true,true) @@ -250,6 +251,99 @@ StringIDRef StringHasher::getID(const QByteArray &data, bool binary, bool hashab return StringIDRef(insert(sid)); } +StringIDRef StringHasher::getID(const Data::MappedName &name, + const QVector & sids) +{ + StringID d; + d._postfix = name.postfixBytes(); + + Data::IndexedName indexed; + if (!d._postfix.size()) + indexed = Data::IndexedName(name.dataBytes()); + if (indexed) + d._data = QByteArray::fromRawData(indexed.getType(), strlen(indexed.getType())); + else + d._data = name.dataBytes(); + + auto it = _hashes->left.find(&d); + if(it!=_hashes->left.end()) { + auto res = StringIDRef(it->first); + if (indexed) + res._index = indexed.getIndex(); + return res; + } + + if (!indexed && name.isRaw()) + d._data = QByteArray(name.dataBytes().constData(), + name.dataBytes().size()); + + StringIDRef postfixRef; + if (d._postfix.size() && d._postfix.indexOf("#") < 0) { + postfixRef = getID(d._postfix, false, false); + postfixRef.toBytes(d._postfix); + } + + StringIDRef indexRef; + if (indexed) + indexRef = getID(d._data, false, false); + + StringIDRef sid(new StringID(lastID()+1,d._data,false,false)); + StringID & id = *sid._sid; + if (d._postfix.size()) { + id._flags.set(StringID::Postfixed); + id._postfix = d._postfix; + } + + int count = 0; + for (auto & s : sids) { + if (s && s._sid->_hasher == this) + ++count; + } + + int extra = (postfixRef ? 1 : 0) + (indexRef ? 1 : 0); + if (count == sids.size() && !postfixRef && !indexRef) + id._sids = sids; + else { + id._sids.reserve(count + extra); + if (postfixRef) { + id._flags.set(StringID::PostfixEncoded); + id._sids.push_back(postfixRef); + } + if (indexRef) { + id._flags.set(StringID::Indexed); + id._sids.push_back(indexRef); + } + for (auto &s : sids) { + if (s && s._sid->_hasher == this) + id._sids.push_back(s); + } + } + if (id._sids.size() > 10) { + std::sort(id._sids.begin()+extra, id._sids.end()); + id._sids.erase(std::unique(id._sids.begin()+extra, id._sids.end()), id._sids.end()); + } + + if (id._postfix.size() && !indexed) { + StringID::IndexID res = StringID::fromString(id._data); + if (res.id > 0) { + int offset = id.isPostfixEncoded() ? 1 : 0; + for (int i=offset; i #include +namespace Data{ +class MappedName; +} + namespace App { class StringHasher; @@ -426,6 +430,10 @@ public: /** Map text or binary data to an integer */ StringIDRef getID(const QByteArray & data, bool binary, bool hashable=true, bool nocopy=false); + /** Map geometry element name to an integer */ + StringIDRef getID(const Data::MappedName & name, + const QVector & sids); + /** Obtain the reference counted StringID object from numerical id * * This function exists because the stored string may be one way hashed, diff --git a/src/App/StringIDPy.xml b/src/App/StringIDPy.xml index 2ef9a0005b..0de7e93e3b 100644 --- a/src/App/StringIDPy.xml +++ b/src/App/StringIDPy.xml @@ -26,6 +26,12 @@ + + + Return the related string IDs + + + Return the data associated with this ID diff --git a/src/App/StringIDPyImp.cpp b/src/App/StringIDPyImp.cpp index b1d6d9e87b..8f7e1e670e 100644 --- a/src/App/StringIDPyImp.cpp +++ b/src/App/StringIDPyImp.cpp @@ -51,6 +51,13 @@ Py::Int StringIDPy::getValue(void) const { return Py::Int(getStringIDPtr()->value()); } +Py::List StringIDPy::getRelated(void) const { + Py::List list; + for (auto &id : getStringIDPtr()->relatedIDs()) + list.append(Py::Long(id.value())); + return list; +} + Py::String StringIDPy::getData(void) const { return Py::String(getStringIDPtr()->dataToText(this->_index)); } diff --git a/src/Base/Tools.h b/src/Base/Tools.h index c5a6826941..50f2a13caf 100644 --- a/src/Base/Tools.h +++ b/src/Base/Tools.h @@ -240,6 +240,31 @@ public: } }; +// ---------------------------------------------------------------------------- + +/** Temporary shorten a sub-object path for more efficient traversal */ +class StringGuard { +public: + StringGuard(char *c) + :c(c) + { + v1 = c[0]; + v2 = c[1]; + c[0] = '.'; + c[1] = 0; + } + ~StringGuard() + { + c[0] = v1; + c[1] = v2; + } + + char *c; + char v1; + char v2; +}; + + // ---------------------------------------------------------------------------- struct BaseExport Tools