diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index f530a9bac7..8ddc6aa014 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -87,6 +87,13 @@ public: TopoDS_Shape Shape; }; +/// When tracing an element's history, one can either stop the trace when the element's type +/// changes, or continue tracing the history through the change. This enumeration replaces a boolean +/// parameter in the original Toponaming branch by realthunder. +enum class HistoryTraceType { + stopOnTypeChange, + followTypeChange +}; /** The representation for a CAD Shape @@ -382,14 +389,14 @@ public: return ret; } - static TopoDS_Shape &move(TopoDS_Shape &s, const TopLoc_Location &); - static TopoDS_Shape moved(const TopoDS_Shape &s, const TopLoc_Location &); - static TopoDS_Shape &move(TopoDS_Shape &s, const gp_Trsf &); - static TopoDS_Shape moved(const TopoDS_Shape &s, const gp_Trsf &); - static TopoDS_Shape &locate(TopoDS_Shape &s, const TopLoc_Location &loc); - static TopoDS_Shape located(const TopoDS_Shape &s, const TopLoc_Location &); - static TopoDS_Shape &locate(TopoDS_Shape &s, const gp_Trsf &); - static TopoDS_Shape located(const TopoDS_Shape &s, const gp_Trsf &); + static TopoDS_Shape& move(TopoDS_Shape& tds, const TopLoc_Location& loc); + static TopoDS_Shape moved(const TopoDS_Shape& tds, const TopLoc_Location& loc); + static TopoDS_Shape& move(TopoDS_Shape& tds, const gp_Trsf& transfer); + static TopoDS_Shape moved(const TopoDS_Shape& tds, const gp_Trsf& transfer); + static TopoDS_Shape& locate(TopoDS_Shape& tds, const TopLoc_Location& loc); + static TopoDS_Shape located(const TopoDS_Shape& tds, const TopLoc_Location& loc); + static TopoDS_Shape& locate(TopoDS_Shape& tds, const gp_Trsf& transfer); + static TopoDS_Shape located(const TopoDS_Shape& tds, const gp_Trsf& transfer); TopoShape &makeGTransform(const TopoShape &shape, const Base::Matrix4D &mat, const char *op=nullptr, bool copy=false); @@ -418,7 +425,7 @@ public: * improve performance. */ //@{ - void initCache(int reset=0, const char *file=nullptr, int line=0) const; + void initCache(int reset=0) const; int findShape(const TopoDS_Shape &subshape) const; TopoDS_Shape findShape(const char *name) const; TopoDS_Shape findShape(TopAbs_ShapeEnum type, int idx) const; diff --git a/src/Mod/Part/App/TopoShapeCache.cpp b/src/Mod/Part/App/TopoShapeCache.cpp index 11781c5da1..ec82c84c02 100644 --- a/src/Mod/Part/App/TopoShapeCache.cpp +++ b/src/Mod/Part/App/TopoShapeCache.cpp @@ -22,4 +22,234 @@ * * ***************************************************************************/ +#include "PreCompiled.h" #include "TopoShapeCache.h" + +using namespace Part; + +ShapeRelationKey::ShapeRelationKey(Data::MappedName name, HistoryTraceType historyTraceType) + : name(std::move(name)) + , historyTraceType(historyTraceType) +{} + +bool ShapeRelationKey::operator<(const ShapeRelationKey& other) const +{ + if (historyTraceType != other.historyTraceType) { + return historyTraceType < other.historyTraceType; + } + return name < other.name; +} + +TopoShape TopoShapeCache::Ancestry::_getTopoShape(const TopoShape& parent, int index) +{ + auto& ts = topoShapes[index - 1]; + if (ts.isNull()) { + ts.setShape(shapes.FindKey(index), true); + ts.initCache(); + ts._cache->subLocation = ts._Shape.Location(); + } + + if (ts._Shape.IsEqual(parent._cache->shape)) { + return parent; + } + + TopoShape res(ts); + res.Tag = parent.Tag; + res.Hasher = parent.Hasher; + + if (!parent.getShape().Location().IsIdentity()) { + res.setShape(TopoShape::moved(res._Shape, parent.getShape().Location()), false); + } + + if (ts._cache->cachedElementMap) { + res.resetElementMap(ts._cache->cachedElementMap); + } + else if (parent._parentCache) { + // If no cachedElementMap exists, we use _parentCache for + // delayed generation of sub element map so that we don't need + // to always generate a full map whenever we return a sub + // shape. To simplify the mapping and avoid circular + // dependency, we do not chain parent and grandparent. + // Instead, we always use the cache from the top parent. And to + // make it work, we must accumulate the TopLoc_Location along + // the lineage, which is required for OCCT shape mapping to + // work. + // + // Cache::subLocation is shared and only contains the location + // in the direct parent shape, while TopoShape::_subLocation is + // used to accumulate locations in higher ancestors. We + // separate these two to avoid invalidating cache. + + res._subLocation = parent._subLocation * parent._cache->subLocation; + res._parentCache = parent._parentCache; + } + else { + res._parentCache = owner->shared_from_this(); + } + return res; +} + + +void TopoShapeCache::Ancestry::clear() +{ + topoShapes.clear(); +} + +TopoShape TopoShapeCache::Ancestry::getTopoShape(const TopoShape& parent, int index) +{ + TopoShape res; + if (index <= 0 || index > shapes.Extent()) { + return res; + } + topoShapes.resize(shapes.Extent()); + return _getTopoShape(parent, index); +} + +std::vector TopoShapeCache::Ancestry::getTopoShapes(const TopoShape& parent) +{ + int count = shapes.Extent(); + std::vector res; + res.reserve(count); + topoShapes.resize(count); + for (int i = 1; i <= count; ++i) { + res.push_back(_getTopoShape(parent, i)); + } + return res; +} + +TopoDS_Shape TopoShapeCache::Ancestry::stripLocation(const TopoDS_Shape& parent, + const TopoDS_Shape& child) +{ + if (parent.Location() != owner->location) { + owner->location = parent.Location(); + owner->locationInverse = parent.Location().Inverted(); + } + return TopoShape::located(child, owner->locationInverse * child.Location()); +} + +int TopoShapeCache::Ancestry::find(const TopoDS_Shape& parent, const TopoDS_Shape& subShape) +{ + if (parent.Location().IsIdentity()) { + return shapes.FindIndex(subShape); + } + return shapes.FindIndex(stripLocation(parent, subShape)); +} + +TopoDS_Shape TopoShapeCache::Ancestry::find(const TopoDS_Shape& parent, int index) +{ + if (index <= 0 || index > shapes.Extent()) { + return {}; + } + if (parent.Location().IsIdentity()) { + return shapes.FindKey(index); + } + return TopoShape::moved(shapes.FindKey(index), parent.Location()); +} + +int TopoShapeCache::Ancestry::count() const +{ + return shapes.Extent(); +} + + +TopoShapeCache::TopoShapeCache(const TopoDS_Shape& tds) + : shape(tds.Located(TopLoc_Location())) +{} + +void TopoShapeCache::insertRelation(const ShapeRelationKey& key, + const QVector& value) +{ + auto [insertedItr, newKeyInserted] = relations.insert({key, value}); + if (newKeyInserted) { + insertedItr->first.name.compact(); + } + else { + insertedItr->second = value; + } +} + +bool TopoShapeCache::isTouched(const TopoDS_Shape& tds) const +{ + return !this->shape.IsPartner(tds) || this->shape.Orientation() != tds.Orientation(); +} + +TopoShapeCache::Ancestry& TopoShapeCache::getAncestry(TopAbs_ShapeEnum type) +{ + auto& ancestry = shapeAncestryCache.at(type); + if (!ancestry.owner) { + ancestry.owner = this; + if (!shape.IsNull()) { + if (type == TopAbs_SHAPE) { + for (TopoDS_Iterator it(shape); it.More(); it.Next()) { + ancestry.shapes.Add(it.Value()); + } + } + else { + TopExp::MapShapes(shape, type, ancestry.shapes); + } + } + } + return ancestry; +} + +int TopoShapeCache::countShape(TopAbs_ShapeEnum type) +{ + if (shape.IsNull()) { + return 0; + } + return getAncestry(type).count(); +} + +int TopoShapeCache::findShape(const TopoDS_Shape& parent, const TopoDS_Shape& subShape) +{ + if (shape.IsNull() || subShape.IsNull()) { + return 0; + } + return getAncestry(subShape.ShapeType()).find(parent, subShape); +} + +TopoDS_Shape TopoShapeCache::findShape(const TopoDS_Shape& parent, TopAbs_ShapeEnum type, int index) +{ + if (!shape.IsNull()) { + return getAncestry(type).find(parent, index); + } + return {}; +} + +TopoDS_Shape TopoShapeCache::findAncestor(const TopoDS_Shape& parent, + const TopoDS_Shape& subShape, + TopAbs_ShapeEnum type, + std::vector* ancestors) +{ + TopoDS_Shape nullShape; + if (shape.IsNull() || subShape.IsNull() || type == TopAbs_SHAPE) { + return nullShape; + } + + auto& info = getAncestry(type); + + auto& ancestorInfo = info.ancestors.at(subShape.ShapeType()); + if (!ancestorInfo.initialized) { + ancestorInfo.initialized = true; + // ancestorInfo.shapes is the output variable here, storing (and caching) the actual map + TopExp::MapShapesAndAncestors(shape, subShape.ShapeType(), type, ancestorInfo.shapes); + } + int index = parent.Location().IsIdentity() + ? ancestorInfo.shapes.FindIndex(subShape) + : ancestorInfo.shapes.FindIndex(info.stripLocation(parent, subShape)); + if (index == 0) { + return nullShape; + } + const auto& shapes = ancestorInfo.shapes.FindFromIndex(index); + if (shapes.Extent() == 0) { + return nullShape; + } + + if (ancestors) { + ancestors->reserve(ancestors->size() + shapes.Extent()); + for (TopTools_ListIteratorOfListOfShape it(shapes); it.More(); it.Next()) { + ancestors->push_back(TopoShape::moved(it.Value(), parent.Location())); + } + } + return TopoShape::moved(shapes.First(), parent.Location()); +} diff --git a/src/Mod/Part/App/TopoShapeCache.h b/src/Mod/Part/App/TopoShapeCache.h index 7109f18c7d..40beddf648 100644 --- a/src/Mod/Part/App/TopoShapeCache.h +++ b/src/Mod/Part/App/TopoShapeCache.h @@ -29,14 +29,15 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -# include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif #include @@ -46,247 +47,102 @@ namespace Part { -struct ShapeRelationKey { +struct PartExport ShapeRelationKey +{ Data::MappedName name; - bool sameType; + HistoryTraceType historyTraceType; - ShapeRelationKey(const Data::MappedName & name, bool sameType) - :name(name), sameType(sameType) - {} - - bool operator<(const ShapeRelationKey &other) const { - if(sameType != other.sameType) - return sameType; - return name < other.name; - } + ShapeRelationKey(Data::MappedName name, HistoryTraceType historyTraceType); + bool operator<(const ShapeRelationKey& other) const; }; -class TopoShapeCache: public std::enable_shared_from_this +class PartExport TopoShapeCache: public std::enable_shared_from_this { public: + /// Reference counted element map for the owner TopoShape. The ElementMap of + /// a TopoShape is normally accessed through the inherited member function + /// ComplexGeoData::elementMap(). The extra shared pointer here is so that + /// other TopoShape instances with the same Cache can reuse the map once + /// generated. Data::ElementMapPtr cachedElementMap; - TopLoc_Location subLocation; - TopoDS_Shape shape; - TopLoc_Location loc; - TopLoc_Location locInv; - std::size_t memsize = 0; + /// Location of the original cached TopoDS_Shape. + TopLoc_Location subLocation; + + /// The cached TopoDS_Shape stripped of any location (i.e. a null TopoDS_Shape::myLocation). + TopoDS_Shape shape; + + /// Location of the last ancestor shape used to find this TopoShape. These two members are used + /// to avoid repetitive inverting the location of the same ancestor. + TopLoc_Location location; + + /// Inverse of location + TopLoc_Location locationInverse; struct AncestorInfo { - bool inited = false; + bool initialized = false; TopTools_IndexedDataMapOfShapeListOfShape shapes; }; - class Info + + /// Class for caching the ancestor and children shapes mapping + class Ancestry { private: - TopoShapeCache* owner = 0; + TopoShapeCache* owner = nullptr; + + /// OCCT map from the owner TopoShape to a list of children (i.e. lower hierarchical) + /// TopoDS_Shape TopTools_IndexedMapOfShape shapes; + + /// One-to-one corresponding TopoShape to each child TopoDS_Shape std::vector topoShapes; + + /// Caches the OCCT ancestor shape maps, e.g. + /// Cache::shapeAncestryCache[TopAbs_FACE].ancestors[TopAbs_EDGE] + /// stores an OCCT TopTools_IndexedDataMapOfShapeListOfShape that can return a list of + /// faces containing a given edge. std::array ancestors; - TopoShape _getTopoShape(const TopoShape& parent, int index) - { - auto& s = topoShapes[index - 1]; - if (s.isNull()) { - s.setShape(shapes.FindKey(index), true); - s.initCache(); - s._cache->subLocation = s._Shape.Location(); - } - - if (s._Shape.IsEqual(parent._cache->shape)) - return parent; - - TopoShape res(s); - res.Tag = parent.Tag; - res.Hasher = parent.Hasher; - - if (!parent.getShape().Location().IsIdentity()) - res.setShape(TopoShape::moved(res._Shape, parent.getShape().Location()), false); - - if (s._cache->cachedElementMap) - res.resetElementMap(s._cache->cachedElementMap); - else if (parent._parentCache) { - // If no cachedElementMap exists, we use _parentCache for - // delayed generation of sub element map so that we don't need - // to always generate a full map whenever we return a sub - // shape. To simplify the mapping and avoid circular - // dependency, we do not chain parent and grandparent. - // Instead, we always use the cache from the top parent. And to - // make it work, we must accumulate the TopLoc_Location along - // the lineage, which is required for OCCT shape mapping to - // work. - // - // Cache::subLocation is shared and only contains the location - // in the direct parent shape, while TopoShape::_subLocation is - // used to accumulate locations in higher ancestors. We - // separate these two to avoid invalidating cache. - - res._subLocation = parent._subLocation * parent._cache->subLocation; - res._parentCache = parent._parentCache; - } - else - res._parentCache = owner->shared_from_this(); - return res; - } + TopoShape _getTopoShape(const TopoShape& parent, int index); public: - void clear() - { - topoShapes.clear(); - } - - TopoShape getTopoShape(const TopoShape& parent, int index) - { - TopoShape res; - if (index <= 0 || index > shapes.Extent()) - return res; - topoShapes.resize(shapes.Extent()); - return _getTopoShape(parent, index); - } - - std::vector getTopoShapes(const TopoShape& parent) - { - int count = shapes.Extent(); - std::vector res; - res.reserve(count); - topoShapes.resize(count); - for (int i = 1; i <= count; ++i) - res.push_back(_getTopoShape(parent, i)); - return res; - } - - TopoDS_Shape stripLocation(const TopoDS_Shape& parent, const TopoDS_Shape& child) - { - if (parent.Location() != owner->loc) { - owner->loc = parent.Location(); - owner->locInv = parent.Location().Inverted(); - } - return TopoShape::located(child, owner->locInv * child.Location()); - } - - int find(const TopoDS_Shape& parent, const TopoDS_Shape& subshape) - { - if (parent.Location().IsIdentity()) - return shapes.FindIndex(subshape); - return shapes.FindIndex(stripLocation(parent, subshape)); - } - - TopoDS_Shape find(const TopoDS_Shape& parent, int index) - { - if (index <= 0 || index > shapes.Extent()) - return TopoDS_Shape(); - if (parent.Location().IsIdentity()) - return shapes.FindKey(index); - else - return TopoShape::moved(shapes.FindKey(index), parent.Location()); - } - - int count() const - { - return shapes.Extent(); - } + void clear(); + TopoShape getTopoShape(const TopoShape& parent, int index); + std::vector getTopoShapes(const TopoShape& parent); + TopoDS_Shape stripLocation(const TopoDS_Shape& parent, const TopoDS_Shape& child); + int find(const TopoDS_Shape& parent, const TopoDS_Shape& subShape); + TopoDS_Shape find(const TopoDS_Shape& parent, int index); + int count() const; friend TopoShapeCache; }; - std::array infos; - std::map> relations; - - TopoShapeCache(const TopoDS_Shape& s) - : shape(s.Located(TopLoc_Location())) - {} - - void insertRelation(const ShapeRelationKey& key, const QVector& value) - { - auto res = relations.insert(std::make_pair(key, value)); - if (res.second) - res.first->first.name.compact(); - else - res.first->second = value; - } - - bool isTouched(const TopoDS_Shape& s) - { - return !this->shape.IsPartner(s) || this->shape.Orientation() != s.Orientation(); - } - - Info& getInfo(TopAbs_ShapeEnum type) - { - auto& info = infos[type]; - if (!info.owner) { - info.owner = this; - if (!shape.IsNull()) { - if (type == TopAbs_SHAPE) { - for (TopoDS_Iterator it(shape); it.More(); it.Next()) - info.shapes.Add(it.Value()); - } - else - TopExp::MapShapes(shape, type, info.shapes); - } - } - return info; - } - - int countShape(TopAbs_ShapeEnum type) - { - if (shape.IsNull()) - return 0; - return getInfo(type).count(); - } - - int findShape(const TopoDS_Shape& parent, const TopoDS_Shape& subshape) - { - if (shape.IsNull() || subshape.IsNull()) - return 0; - return getInfo(subshape.ShapeType()).find(parent, subshape); - } - - TopoDS_Shape findShape(const TopoDS_Shape& parent, TopAbs_ShapeEnum type, int index) - { - if (!shape.IsNull()) - return getInfo(type).find(parent, index); - return TopoDS_Shape(); - } + explicit TopoShapeCache(const TopoDS_Shape& tds); + void insertRelation(const ShapeRelationKey& key, const QVector& value); + bool isTouched(const TopoDS_Shape& tds) const; + Ancestry& getAncestry(TopAbs_ShapeEnum type); + int countShape(TopAbs_ShapeEnum type); + int findShape(const TopoDS_Shape& parent, const TopoDS_Shape& subShape); + TopoDS_Shape findShape(const TopoDS_Shape& parent, TopAbs_ShapeEnum type, int index); + /// Given a parent shape and a child (sub) shape, call TopExp::MapShapesAndAncestors and cache + /// the result. Subsequent calls to this method given unchanged geometry will use the cached + /// data rather than re-running MapShapesAndAncestors. + /// If ancestors is given, it is cleared and overwritten with the ancestry data. TopoDS_Shape findAncestor(const TopoDS_Shape& parent, - const TopoDS_Shape& subshape, + const TopoDS_Shape& subShape, TopAbs_ShapeEnum type, - std::vector* ancestors = 0) - { - TopoDS_Shape ret; - if (shape.IsNull() || subshape.IsNull() || type == TopAbs_SHAPE) - return ret; + std::vector* ancestors = nullptr); - auto& info = getInfo(type); + /// Ancestor and children shape caches of all shape types. Note that + /// shapeAncestryCache[TopAbs_SHAPE] is also valid and stores the direct children of a + /// compound shape. + std::array shapeAncestryCache; - auto& ainfo = info.ancestors[subshape.ShapeType()]; - if (!ainfo.inited) { - ainfo.inited = true; - TopExp::MapShapesAndAncestors(shape, subshape.ShapeType(), type, ainfo.shapes); - } - int index; - if (parent.Location().IsIdentity()) - index = ainfo.shapes.FindIndex(subshape); - else - index = ainfo.shapes.FindIndex(info.stripLocation(parent, subshape)); - if (!index) - return ret; - const auto& shapes = ainfo.shapes.FindFromIndex(index); - if (!shapes.Extent()) - return ret; - - if (ancestors) { - ancestors->reserve(ancestors->size() + shapes.Extent()); - for (TopTools_ListIteratorOfListOfShape it(shapes); it.More(); it.Next()) - ancestors->push_back(TopoShape::moved(it.Value(), parent.Location())); - } - return TopoShape::moved(shapes.First(), parent.Location()); - } - - std::size_t getMemSize(); + std::map> relations; }; -} +} // namespace Part #endif // FREECAD_TOPOSHAPECACHE_H diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 6ade6aa2b4..f935f4bd87 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -22,22 +22,112 @@ * * ***************************************************************************/ +#include "PreCompiled.h" + #include "TopoShape.h" #include "TopoShapeCache.h" +FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT + namespace Part { -void TopoShape::setShape(const TopoDS_Shape& shape, bool resetElementMap) +void TopoShape::initCache(int reset) const { - if (resetElementMap) - this->resetElementMap(); - else if (_cache && _cache->isTouched(shape)) - this->flushElementMap(); - //_Shape._Shape = shape; // TODO: Replace the next line with this once ShapeProtector is available. - _Shape = shape; - if (_cache) - initCache(); + if (reset > 0 || !_cache || _cache->isTouched(_Shape)) { + if (_parentCache) { + _parentCache.reset(); + _subLocation.Identity(); + } + _cache = std::make_shared(_Shape); + } } -} \ No newline at end of file +void TopoShape::setShape(const TopoDS_Shape& shape, bool resetElementMap) +{ + if (resetElementMap) { + this->resetElementMap(); + } + else if (_cache && _cache->isTouched(shape)) { + this->flushElementMap(); + } + //_Shape._Shape = shape; // TODO: Replace the next line with this once ShapeProtector is + // available. + _Shape = shape; + if (_cache) { + initCache(); + } +} + + +TopoDS_Shape& TopoShape::move(TopoDS_Shape& tds, const TopLoc_Location& location) +{ +#if OCC_VERSION_HEX < 0x070600 + tds.Move(location); +#else + tds.Move(location, false); +#endif + return tds; +} + +TopoDS_Shape TopoShape::moved(const TopoDS_Shape& tds, const TopLoc_Location& location) +{ +#if OCC_VERSION_HEX < 0x070600 + return tds.Moved(location); +#else + return tds.Moved(location, false); +#endif +} + +TopoDS_Shape& TopoShape::move(TopoDS_Shape& tds, const gp_Trsf& transfer) +{ +#if OCC_VERSION_HEX < 0x070600 + static constexpr double scalePrecision {1e-14}; + if (std::abs(transfer.ScaleFactor()) > scalePrecision) +#else + if (std::abs(transfer.ScaleFactor()) > TopLoc_Location::ScalePrec()) +#endif + { + auto transferCopy(transfer); + transferCopy.SetScaleFactor(1.0); + tds.Move(transferCopy); + } + else { + tds.Move(transfer); + } + return tds; +} + +TopoDS_Shape TopoShape::moved(const TopoDS_Shape& tds, const gp_Trsf& transfer) +{ + TopoDS_Shape sCopy(tds); + return move(sCopy, transfer); +} + +TopoDS_Shape& TopoShape::locate(TopoDS_Shape& tds, const TopLoc_Location& loc) +{ + tds.Location(TopLoc_Location()); + return move(tds, loc); +} + +TopoDS_Shape TopoShape::located(const TopoDS_Shape& tds, const TopLoc_Location& loc) +{ + auto sCopy(tds); + sCopy.Location(TopLoc_Location()); + return moved(sCopy, loc); +} + +TopoDS_Shape& TopoShape::locate(TopoDS_Shape& tds, const gp_Trsf& transfer) +{ + tds.Location(TopLoc_Location()); + return move(tds, transfer); +} + +TopoDS_Shape TopoShape::located(const TopoDS_Shape& tds, const gp_Trsf& transfer) +{ + auto sCopy(tds); + sCopy.Location(TopLoc_Location()); + return moved(sCopy, transfer); +} + +} // namespace Part diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index f8169423c6..ca7e8e69b4 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -3,4 +3,5 @@ target_sources( Part_tests_run PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp ) diff --git a/tests/src/Mod/Part/App/TopoShapeCache.cpp b/tests/src/Mod/Part/App/TopoShapeCache.cpp new file mode 100644 index 0000000000..d75dfc1861 --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeCache.cpp @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) + +TEST(ShapeRelationKey, HistoryTraceTypeComparison) +{ + // Arrange + Data::MappedName mappedName {"mappedName"}; + Part::HistoryTraceType htt1 {Part::HistoryTraceType::stopOnTypeChange}; + Part::HistoryTraceType htt2 {Part::HistoryTraceType::followTypeChange}; + Part::ShapeRelationKey key1 {mappedName, htt1}; + Part::ShapeRelationKey key2 {mappedName, htt2}; + + // Act + bool key1LessThanKey2 = key1 < key2; + + // Assert + ASSERT_TRUE(key1LessThanKey2); +} + +class TopoShapeCacheTest: public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + _docName = App::GetApplication().getUniqueDocumentName("test"); + App::GetApplication().newDocument(_docName.c_str(), "testUser"); + _sids = &_sid; + _hasher = Base::Reference(new App::StringHasher); + ASSERT_EQ(_hasher.getRefCount(), 1); + } + + void TearDown() override + { + App::GetApplication().closeDocument(_docName.c_str()); + } + +private: + std::string _docName; + Data::ElementIDRefs _sid; + QVector* _sids = nullptr; + App::StringHasherRef _hasher; +}; + +/* +Methods to test + ------------- +DONE - explicit TopoShapeCache(const TopoDS_Shape& tds); +DONE - void insertRelation(const ShapeRelationKey& key, const QVector& value); +DONE - bool isTouched(const TopoDS_Shape& tds) const; +DONE Ancestry& getAncestry(TopAbs_ShapeEnum type); +DONE int countShape(TopAbs_ShapeEnum type); +DONE int findShape(const TopoDS_Shape& parent, const TopoDS_Shape& subShape); +DONE TopoDS_Shape findShape(const TopoDS_Shape& parent, TopAbs_ShapeEnum type, int index); +TopoDS_Shape findAncestor(const TopoDS_Shape& parent, + const TopoDS_Shape& subShape, + TopAbs_ShapeEnum type, + std::vector* ancestors = nullptr); + */ + +TEST_F(TopoShapeCacheTest, ConstructionFromTopoDS_Shape) +{ + // Arrange - create a TopoDS shape with some location transformation applied + TopoDS_Vertex vertex; + gp_Quaternion quaternion(1.0, 2.0, 3.0, 4.0); + gp_Trsf transform; + transform.SetRotation(quaternion); + auto location = TopLoc_Location(transform); + vertex.Location(location); + + // Act + auto cache = Part::TopoShapeCache(vertex); + + // Assert - ensure the location of the cached shape was zeroed out + EXPECT_NE(cache.shape.Location(), vertex.Location()); +} + +TEST_F(TopoShapeCacheTest, InsertRelationIntoEmptyTableCompacts) +{ + // Arrange + Data::IndexedName indexedName {"EDGE1"}; + auto mappedName = + Data::MappedName::fromRawData("#94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F"); + ASSERT_TRUE(mappedName.isRaw()); + Data::MappedElement mappedElement1 {indexedName, mappedName}; + QVector vectorOfElements {mappedElement1}; + TopoDS_Vertex vertex; + Part::TopoShapeCache cache(vertex); + Part::ShapeRelationKey key {mappedName, Part::HistoryTraceType::followTypeChange}; + + // Act + cache.insertRelation(key, vectorOfElements); + + // Assert + auto foundIterator = cache.relations.find(key); + EXPECT_NE(foundIterator, cache.relations.end()); + EXPECT_FALSE(foundIterator->first.name.isRaw()); // compact() was called +} + +TEST_F(TopoShapeCacheTest, InsertAlreadyExistsUpdatesExisting) +{ + // Arrange + Data::IndexedName indexedName {"EDGE1"}; + Data::MappedName mappedName("#94;:G0;XTR;:H19:8,F;:H1a,F;BND:-1:0;:H1b:10,F"); + Data::MappedElement mappedElement1 {indexedName, mappedName}; + QVector vectorOfElements {mappedElement1}; + TopoDS_Vertex vertex; + Part::TopoShapeCache cache(vertex); + Part::ShapeRelationKey key {mappedName, Part::HistoryTraceType::followTypeChange}; + + // Act + cache.insertRelation(key, vectorOfElements); + QVector emptyVector; + cache.insertRelation(key, emptyVector); + + // Assert + EXPECT_TRUE(cache.relations.find(key)->second.empty()); +} + +TEST_F(TopoShapeCacheTest, IsTouchedNotPartners) +{ + // Arrange + BRep_TVertex* vertex1 = new BRep_TVertex; + vertex1->Pnt(gp_Pnt(1.0, 1.0, 1.0)); + BRep_TVertex* vertex2 = new BRep_TVertex; + vertex2->Pnt(gp_Pnt(2.0, 2.0, 2.0)); + opencascade::handle handle1(vertex1); + opencascade::handle handle2(vertex2); + TopoDS_Vertex tds1; + TopoDS_Vertex tds2; + tds1.TShape(handle1); + tds2.TShape(handle2); + ASSERT_FALSE(tds1.IsPartner(tds2)); + Part::TopoShapeCache cache(tds1); + + // Act & Assert + EXPECT_TRUE(cache.isTouched(tds2)); +} + +TEST_F(TopoShapeCacheTest, IsTouchedArePartners) +{ + // Arrange + BRep_TVertex* vertex1 = new BRep_TVertex; + vertex1->Pnt(gp_Pnt(1.0, 1.0, 1.0)); + opencascade::handle handle1(vertex1); + TopoDS_Vertex tds1; + TopoDS_Vertex tds2; + tds1.TShape(handle1); + tds2.TShape(handle1); + ASSERT_TRUE(tds1.IsPartner(tds2)); + Part::TopoShapeCache cache(tds1); + + // Act & Assert + EXPECT_FALSE(cache.isTouched(tds2)); +} + +std::tuple> CreateShapeWithSubshapes() +{ + auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)).Edge(); + auto edge2 = BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 0.0, 0.0), gp_Pnt(2.0, 0.0, 0.0)).Edge(); + auto fuse = BRepAlgoAPI_Fuse(edge1, edge2); + fuse.Build(); + return {fuse.Shape(), {edge1, edge2}}; +} + +TEST_F(TopoShapeCacheTest, GetAncestrySHAPE) +{ + // Arrange + auto shape = std::get<0>(CreateShapeWithSubshapes()); + Part::TopoShapeCache cache(shape); + + // Act + auto ancestry = cache.getAncestry(TopAbs_SHAPE); + + // Assert + EXPECT_EQ(2, ancestry.count()); +} + +TEST_F(TopoShapeCacheTest, GetAncestryEDGE) +{ + // Arrange + auto shape = std::get<0>(CreateShapeWithSubshapes()); + Part::TopoShapeCache cache(shape); + + // Act + auto ancestry = cache.getAncestry(TopAbs_EDGE); + + // Assert + EXPECT_EQ(2, ancestry.count()); +} + +TEST_F(TopoShapeCacheTest, GetAncestryFACE) +{ + // Arrange + auto shape = std::get<0>(CreateShapeWithSubshapes()); + Part::TopoShapeCache cache(shape); + + // Act + auto ancestry = cache.getAncestry(TopAbs_FACE); + + // Assert + EXPECT_EQ(0, ancestry.count()); +} + +TEST_F(TopoShapeCacheTest, CountShape) +{ + // Arrange + auto shape = std::get<0>(CreateShapeWithSubshapes()); + Part::TopoShapeCache cache(shape); + + // Act + int countOfEdges = cache.countShape(TopAbs_EDGE); + int countOfFaces = cache.countShape(TopAbs_FACE); + int countOfShapes = cache.countShape(TopAbs_SHAPE); + + // Assert + EXPECT_EQ(2, countOfEdges); + EXPECT_EQ(0, countOfFaces); + EXPECT_EQ(2, countOfShapes); +} + +TEST_F(TopoShapeCacheTest, FindShapeGivenSubshape) +{ + // Arrange + const auto [shape, ancestors] = CreateShapeWithSubshapes(); + Part::TopoShapeCache cache(shape); + + // Act + auto shapeResult1 = cache.findShape(ancestors.first, shape); + auto shapeResult2 = cache.findShape(ancestors.second, shape); + + // Assert + EXPECT_NE(0, shapeResult1); + EXPECT_NE(0, shapeResult2); +} + +TEST_F(TopoShapeCacheTest, FindShapeGivenTypeAndIndex) +{ + // Arrange + const auto [shape, ancestors] = CreateShapeWithSubshapes(); + Part::TopoShapeCache cache(shape); + + // Act + auto shapeResult = cache.findShape(ancestors.first, TopAbs_EDGE, 1); // NOT zero-indexed! + + // Assert + EXPECT_FALSE(shapeResult.IsNull()); +} + +std::tuple> CreateFusedCubes() +{ + auto boxMaker1 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker1.Build(); + auto box1 = boxMaker1.Shape(); + + auto boxMaker2 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker2.Build(); + auto box2 = boxMaker2.Shape(); + auto transform = gp_Trsf(); + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)); + box2.Location(TopLoc_Location(transform)); + + auto fuse = BRepAlgoAPI_Fuse(box1, box2); + fuse.Build(); + + return {fuse, {box1, box2}}; +} + +TEST_F(TopoShapeCacheTest, FindAncestor) +{ + // Arrange + const auto [shape, ancestors] = CreateFusedCubes(); + Part::TopoShapeCache cache(shape); + + // Act + auto ancestorResultCompound = cache.findAncestor(ancestors.first, shape, TopAbs_COMPOUND); + + // Assert + EXPECT_FALSE(ancestorResultCompound.IsNull()); +} + +// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)