Part/Toponaming: Clang-tidy cleanup of TopoShapeCache

Part/Toponaming: Move implementions to cpp file
Part/Toponaming: Automated code cleanup
Part/Toponaming: Address boolean blindess with HistoryTraceType
Part/Toponaming: More linter cleanup
Part/Toponaming: Add located and moved methods
Part/Toponaming: Moved and Located
Part/Toponaming: Add missing methods
Part/Toponaming: Add test framework for TopoShapeCache
Part/Toponaming: Add documentation provided by realthunder
Part/Toponaming: Extend tests for TopoShapeCache
Part/Toponaming: Rename getInfo to getAncestry
Part/Toponaming: Fix bug with newer OCCT
Part/Toponaming: Add final tests and tweak naming
Part/Toponaming: Address reviewer comments
This commit is contained in:
Chris Hennes
2023-11-18 11:49:58 -06:00
committed by Chris Hennes
parent 4077c55fae
commit 48a857e808
6 changed files with 723 additions and 237 deletions

View File

@@ -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;

View File

@@ -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<TopoShape> TopoShapeCache::Ancestry::getTopoShapes(const TopoShape& parent)
{
int count = shapes.Extent();
std::vector<TopoShape> 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<Data::MappedElement>& 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<TopoDS_Shape>* 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());
}

View File

@@ -29,14 +29,15 @@
#include "PreCompiled.h"
#ifndef _PreComp_
# include <TopoDS.hxx>
# include <TopoDS_Compound.hxx>
# include <TopoDS_Iterator.hxx>
# include <TopoDS_Solid.hxx>
# include <TopoDS_Vertex.hxx>
# include <TopExp.hxx>
# include <TopExp_Explorer.hxx>
# include <TopTools_ListIteratorOfListOfShape.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopTools_ListIteratorOfListOfShape.hxx>
#include <utility>
#endif
#include <App/ElementMap.h>
@@ -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<TopoShapeCache>
class PartExport TopoShapeCache: public std::enable_shared_from_this<TopoShapeCache>
{
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<TopoShape> 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<AncestorInfo, TopAbs_SHAPE + 1> 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<TopoShape> getTopoShapes(const TopoShape& parent)
{
int count = shapes.Extent();
std::vector<TopoShape> 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<TopoShape> 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<Info, TopAbs_SHAPE + 1> infos;
std::map<ShapeRelationKey, QVector<Data::MappedElement>> relations;
TopoShapeCache(const TopoDS_Shape& s)
: shape(s.Located(TopLoc_Location()))
{}
void insertRelation(const ShapeRelationKey& key, const QVector<Data::MappedElement>& 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<Data::MappedElement>& 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<TopoDS_Shape>* ancestors = 0)
{
TopoDS_Shape ret;
if (shape.IsNull() || subshape.IsNull() || type == TopAbs_SHAPE)
return ret;
std::vector<TopoDS_Shape>* 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<Ancestry, TopAbs_SHAPE + 1> 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<ShapeRelationKey, QVector<Data::MappedElement>> relations;
};
}
} // namespace Part
#endif // FREECAD_TOPOSHAPECACHE_H

View File

@@ -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<TopoShapeCache>(_Shape);
}
}
}
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

View File

@@ -3,4 +3,5 @@ target_sources(
Part_tests_run
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp
)

View File

@@ -0,0 +1,302 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "gtest/gtest.h"
#include <Mod/Part/App/TopoShape.h>
#include <Mod/Part/App/TopoShapeCache.h>
#include <src/App/InitApplication.h>
#include <gp_Quaternion.hxx>
#include <TopoDS_TVertex.hxx>
#include <BRep_TVertex.hxx>
#include <BRep_TEdge.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepAlgoAPI_Fuse.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <TopoDS_Edge.hxx>
// 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<App::StringHasher>(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<App::StringIDRef>* _sids = nullptr;
App::StringHasherRef _hasher;
};
/*
Methods to test
-------------
DONE - explicit TopoShapeCache(const TopoDS_Shape& tds);
DONE - void insertRelation(const ShapeRelationKey& key, const QVector<Data::MappedElement>& 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<TopoDS_Shape>* 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<Data::MappedElement> 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<Data::MappedElement> vectorOfElements {mappedElement1};
TopoDS_Vertex vertex;
Part::TopoShapeCache cache(vertex);
Part::ShapeRelationKey key {mappedName, Part::HistoryTraceType::followTypeChange};
// Act
cache.insertRelation(key, vectorOfElements);
QVector<Data::MappedElement> 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<TopoDS_TShape> handle1(vertex1);
opencascade::handle<TopoDS_TShape> 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<TopoDS_TShape> 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<TopoDS_Shape, std::pair<TopoDS_Shape, TopoDS_Shape>> 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<TopoDS_Shape, std::pair<TopoDS_Shape, TopoDS_Shape>> 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)