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:
committed by
Chris Hennes
parent
4077c55fae
commit
48a857e808
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,4 +3,5 @@ target_sources(
|
||||
Part_tests_run
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp
|
||||
)
|
||||
|
||||
302
tests/src/Mod/Part/App/TopoShapeCache.cpp
Normal file
302
tests/src/Mod/Part/App/TopoShapeCache.cpp
Normal 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)
|
||||
Reference in New Issue
Block a user