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