diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index 5b8721764b92a..bf0ce716ebc89 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -530,6 +530,9 @@ SET(Part_SRCS ProgressIndicator.h TopoShape.cpp TopoShape.h + TopoShapeCache.cpp + TopoShapeCache.h + TopoShapeExpansion.cpp TopoShapeOpCode.h edgecluster.cpp edgecluster.h diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index aa36ac3d956a2..469f7ffb95422 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -47,6 +47,8 @@ class Color; namespace Part { +class TopoShapeCache; + /* A special sub-class to indicate null shapes */ class PartExport NullShapeException : public Base::ValueError @@ -85,6 +87,13 @@ class PartExport ShapeSegment : public Data::Segment 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 @@ -99,8 +108,10 @@ class PartExport TopoShape : public Data::ComplexGeoData TopoShape(const TopoShape&); ~TopoShape() override; - inline void setShape(const TopoDS_Shape& shape) { - this->_Shape = shape; + void setShape(const TopoDS_Shape& shape, bool resetElementMap=true); + + inline void setShape(const TopoShape& shape) { + *this = shape; } inline const TopoDS_Shape& getShape() const { @@ -364,12 +375,29 @@ class PartExport TopoShape : public Data::ComplexGeoData void move(const TopLoc_Location &loc) { _Shape.Move(loc); } + /** Return a new shape that is moved to a new location + * + * @param loc: location + * + * @return Return a shallow copy of the shape moved to the new location + * that is applied in addition to any current transformation of the + * shape + */ TopoShape moved(const TopLoc_Location &loc) const { TopoShape ret(*this); ret._Shape.Move(loc); return ret; } + 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); TopoShape makeGTransform(const Base::Matrix4D &mat, const char *op=nullptr, bool copy=false) const { @@ -388,6 +416,49 @@ class PartExport TopoShape : public Data::ComplexGeoData static const std::string &shapeName(TopAbs_ShapeEnum type,bool silent=false); const std::string &shapeName(bool silent=false) const; static std::pair shapeTypeAndIndex(const char *name); + + + /** @name sub shape cached functions + * + * Mapped element names introduces some overhead when getting sub shapes + * from a shape. These functions use internal caches for sub-shape maps to + * improve performance. + */ + //@{ + 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; + int findAncestor(const TopoDS_Shape &subshape, TopAbs_ShapeEnum type) const; + TopoDS_Shape findAncestorShape(const TopoDS_Shape &subshape, TopAbs_ShapeEnum type) const; + std::vector findAncestors(const TopoDS_Shape &subshape, TopAbs_ShapeEnum type) const; + std::vector findAncestorsShapes(const TopoDS_Shape &subshape, TopAbs_ShapeEnum type) const; + /** Search sub shape + * + * unlike findShape(), the input shape does not have to be an actual + * sub-shape of this shape. The sub-shape is searched by shape geometry + * + * @param subshape: a sub shape to search + * @param names: optional output of found sub shape indexed based name + * @param checkGeometry: whether to compare shape geometry + * @param tol: tolerance to check coincident vertices + * @param atol: tolerance to check for same angles + */ + // TODO: Implement this method and its tests later in Toponaming Phase 3. + //std::vector searchSubShape(const TopoShape &subshape, + // std::vector *names=nullptr, + // bool checkGeometry=true, + // double tol=1e-7, double atol=1e-12) const; + //@} + + friend class TopoShapeCache; + +private: + // Cache storage + mutable std::shared_ptr _parentCache; + mutable std::shared_ptr _cache; + mutable TopLoc_Location _subLocation; + private: TopoDS_Shape _Shape; }; diff --git a/src/Mod/Part/App/TopoShapeCache.cpp b/src/Mod/Part/App/TopoShapeCache.cpp new file mode 100644 index 0000000000000..ec82c84c02a57 --- /dev/null +++ b/src/Mod/Part/App/TopoShapeCache.cpp @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2022 Zheng, Lei * + * Copyright (c) 2023 FreeCAD Project Association * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#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 new file mode 100644 index 0000000000000..190bfe3b28b48 --- /dev/null +++ b/src/Mod/Part/App/TopoShapeCache.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2022 Zheng, Lei * + * Copyright (c) 2023 FreeCAD Project Association * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef FREECAD_TOPOSHAPECACHE_H +#define FREECAD_TOPOSHAPECACHE_H + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include "TopoShape.h" + +namespace Part +{ + +struct PartExport ShapeRelationKey +{ + Data::MappedName name; + HistoryTraceType historyTraceType; + + ShapeRelationKey(Data::MappedName name, HistoryTraceType historyTraceType); + bool operator<(const ShapeRelationKey& other) const; +}; + +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; + + /// 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 PartExport AncestorInfo + { + bool initialized = false; + TopTools_IndexedDataMapOfShapeListOfShape shapes; + }; + + /// Class for caching the ancestor and children shapes mapping + class PartExport Ancestry + { + private: + 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); + + public: + 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; + }; + + 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, + TopAbs_ShapeEnum type, + std::vector* ancestors = nullptr); + + /// 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; + + 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 new file mode 100644 index 0000000000000..09b90fe98fb68 --- /dev/null +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2022 Zheng, Lei * + * Copyright (c) 2023 FreeCAD Project Association * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include +#include "PreCompiled.h" + +#include "TopoShape.h" +#include "TopoShapeCache.h" + +FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT + +namespace Part +{ + +void TopoShape::initCache(int reset) const +{ + if (reset > 0 || !_cache || _cache->isTouched(_Shape)) { + if (_parentCache) { + _parentCache.reset(); + _subLocation.Identity(); + } + _cache = std::make_shared(_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); +} + + +int TopoShape::findShape(const TopoDS_Shape& subshape) const +{ + initCache(); + return _cache->findShape(_Shape, subshape); +} + + +TopoDS_Shape TopoShape::findShape(const char* name) const +{ + if (!name) { + return {}; + } + + Data::MappedElement res = getElementName(name); + if (!res.index) { + return {}; + } + + auto idx = shapeTypeAndIndex(name); + if (idx.second == 0) { + return {}; + } + initCache(); + return _cache->findShape(_Shape, idx.first, idx.second); +} + +TopoDS_Shape TopoShape::findShape(TopAbs_ShapeEnum type, int idx) const +{ + initCache(); + return _cache->findShape(_Shape, type, idx); +} + +int TopoShape::findAncestor(const TopoDS_Shape& subshape, TopAbs_ShapeEnum type) const +{ + initCache(); + return _cache->findShape(_Shape, _cache->findAncestor(_Shape, subshape, type)); +} + +TopoDS_Shape TopoShape::findAncestorShape(const TopoDS_Shape& subshape, TopAbs_ShapeEnum type) const +{ + initCache(); + return _cache->findAncestor(_Shape, subshape, type); +} + +std::vector TopoShape::findAncestors(const TopoDS_Shape& subshape, TopAbs_ShapeEnum type) const +{ + const auto& shapes = findAncestorsShapes(subshape, type); + std::vector ret; + ret.reserve(shapes.size()); + for (const auto& shape : shapes) { + ret.push_back(findShape(shape)); + } + return ret; +} + +std::vector TopoShape::findAncestorsShapes(const TopoDS_Shape& subshape, + TopAbs_ShapeEnum type) const +{ + initCache(); + std::vector shapes; + _cache->findAncestor(_Shape, subshape, type, &shapes); + return shapes; +} + +} // namespace Part diff --git a/src/Mod/TechDraw/App/Cosmetic.h b/src/Mod/TechDraw/App/Cosmetic.h index dfaab332e24d9..efd6c1e8da6f2 100644 --- a/src/Mod/TechDraw/App/Cosmetic.h +++ b/src/Mod/TechDraw/App/Cosmetic.h @@ -23,7 +23,10 @@ #ifndef TECHDRAW_COSMETIC_H #define TECHDRAW_COSMETIC_H +#include + #include +#include #include #include @@ -49,6 +52,16 @@ class TechDrawExport LineFormat const bool visible); ~LineFormat() = default; + int getStyle() const { return m_style; } + void setStyle(int style) { m_style = style; } + double getWidth() const { return m_weight; } + void setWidth(double width) {m_weight = width; } + App::Color getColor() const { return m_color; } + void setColor(App::Color color) { m_color = color; } + QColor getQColor() const { return m_color.asValue(); } + void setQColor(QColor qColor) { m_color.set(qColor.redF(), qColor.greenF(), qColor.blueF(), 1.0 - qColor.alphaF()); } + bool getVisible() const { return m_visible; } + void setVisible(bool viz) { m_visible = viz; } int getLineNumber() const { return m_lineNumber; } void setLineNumber(int number) { m_lineNumber = number; } diff --git a/src/Mod/TechDraw/App/LineGenerator.cpp b/src/Mod/TechDraw/App/LineGenerator.cpp index 398eb28d2e0ec..21952ffd55dda 100644 --- a/src/Mod/TechDraw/App/LineGenerator.cpp +++ b/src/Mod/TechDraw/App/LineGenerator.cpp @@ -113,12 +113,6 @@ QPen LineGenerator::getLinePen(size_t lineNumber, double nominalLineWidth) proportionalAdjust = nominalLineWidth; } - // Note: if the cap style is Round or Square, the lengths of the lines, or - // dots/dashes within the line, will be wrong by 1 pen width. To get the - // exact line lengths or dash pattern, you must use Flat caps. Flat caps - // look terrible at the corners. - linePen.setCapStyle((Qt::PenCapStyle)Preferences::LineCapStyle()); - // valid line numbers are [1, number of line definitions] // line 1 is always (?) continuous // 0 substitutes for LineFormat::InvalidLine here diff --git a/src/Mod/TechDraw/Gui/CommandExtensionDims.cpp b/src/Mod/TechDraw/Gui/CommandExtensionDims.cpp index f35ddf58fcf1b..40cb9569b7244 100644 --- a/src/Mod/TechDraw/Gui/CommandExtensionDims.cpp +++ b/src/Mod/TechDraw/Gui/CommandExtensionDims.cpp @@ -1361,6 +1361,7 @@ void execCreateObliqueChainDimension(Gui::Command* cmd) { std::string edgeTag = objFeat->addCosmeticEdge(oldVertex.point / scale, nextPoint / scale); auto edge = objFeat->getCosmeticEdge(edgeTag); edge->m_format.m_style = 1; + edge->m_format.m_lineNumber = 1; edge->m_format.m_weight = 0.15; edge->m_format.m_color = App::Color(0.0f, 0.0f, 0.0f); } @@ -1716,6 +1717,7 @@ void execCreateObliqueCoordDimension(Gui::Command* cmd) { std::string edgeTag = objFeat->addCosmeticEdge(oldVertex.point / scale, nextPoint / scale); auto edge = objFeat->getCosmeticEdge(edgeTag); edge->m_format.m_style = 1; + edge->m_format.m_lineNumber = 1; edge->m_format.m_weight = 0.15; edge->m_format.m_color = App::Color(0.0f, 0.0f, 0.0f); } diff --git a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp index 01c06822dd2c5..10af1a1998f9e 100644 --- a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp +++ b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp @@ -74,10 +74,10 @@ using DU = DrawUtil; namespace TechDrawGui { -//LineAttributes activeAttributes; // container holding global line attributes +//TechDraw::LineFormat activeAttributes; // container holding global line attributes //internal helper functions -lineAttributes& _getActiveLineAttributes(); +TechDraw::LineFormat& _getActiveLineAttributes(); Base::Vector3d _circleCenter(Base::Vector3d p1, Base::Vector3d p2, Base::Vector3d p3); void _createThreadCircle(std::string Name, TechDraw::DrawViewPart* objFeat, float factor); void _createThreadLines(std::vector SubNames, TechDraw::DrawViewPart* objFeat, @@ -1488,7 +1488,7 @@ void execExtendShortenLine(Gui::Command* cmd, bool extend) toDelete.push_back(uniTag); if (baseGeo->source() == 1) { auto cosEdge = objFeat->getCosmeticEdge(uniTag); - oldStyle = cosEdge->m_format.m_style; + oldStyle = cosEdge->m_format.m_lineNumber; oldWeight = cosEdge->m_format.m_weight; oldColor = cosEdge->m_format.m_color; objFeat->removeCosmeticEdge(toDelete); @@ -1974,9 +1974,9 @@ bool CmdTechDrawExtensionArcLengthAnnotation::isActive() namespace TechDrawGui { -lineAttributes& _getActiveLineAttributes() +LineFormat& _getActiveLineAttributes() { - static lineAttributes attributes; + static TechDraw::LineFormat attributes; return attributes; } @@ -2133,19 +2133,19 @@ void _createThreadLines(std::vector SubNames, TechDraw::DrawViewPar void _setLineAttributes(TechDraw::CosmeticEdge* cosEdge) { // set line attributes of a cosmetic edge - cosEdge->m_format.m_style = _getActiveLineAttributes().getStyle(); - cosEdge->m_format.m_weight = _getActiveLineAttributes().getWidthValue(); - cosEdge->m_format.m_color = _getActiveLineAttributes().getColorValue(); - cosEdge->m_format.m_lineNumber = _getActiveLineAttributes().getStyle(); + cosEdge->m_format.setStyle(_getActiveLineAttributes().getStyle()); + cosEdge->m_format.setWidth(_getActiveLineAttributes().getWidth()); + cosEdge->m_format.setColor(_getActiveLineAttributes().getColor()); + cosEdge->m_format.setLineNumber(_getActiveLineAttributes().getLineNumber()); } void _setLineAttributes(TechDraw::CenterLine* cosEdge) { // set line attributes of a cosmetic edge cosEdge->m_format.m_style = _getActiveLineAttributes().getStyle(); - cosEdge->m_format.m_weight = _getActiveLineAttributes().getWidthValue(); - cosEdge->m_format.m_color = _getActiveLineAttributes().getColorValue(); - cosEdge->m_format.m_lineNumber = _getActiveLineAttributes().getStyle(); + cosEdge->m_format.m_weight = _getActiveLineAttributes().getWidth(); + cosEdge->m_format.m_color = _getActiveLineAttributes().getColor(); + cosEdge->m_format.setLineNumber(_getActiveLineAttributes().getLineNumber()); } void _setLineAttributes(TechDraw::CosmeticEdge* cosEdge, int style, float weight, App::Color color) @@ -2154,16 +2154,16 @@ void _setLineAttributes(TechDraw::CosmeticEdge* cosEdge, int style, float weight cosEdge->m_format.m_style = _getActiveLineAttributes().getStyle(); cosEdge->m_format.m_weight = weight; cosEdge->m_format.m_color = color; - cosEdge->m_format.m_lineNumber = style; + cosEdge->m_format.setLineNumber(style); } void _setLineAttributes(TechDraw::CenterLine* cosEdge, int style, float weight, App::Color color) { // set line attributes of a centerline cosEdge->m_format.m_style = _getActiveLineAttributes().getStyle(); - cosEdge->m_format.m_lineNumber = style; cosEdge->m_format.m_weight = weight; cosEdge->m_format.m_color = color; + cosEdge->m_format.setLineNumber(style); } }// namespace TechDrawGui diff --git a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp index 479801253830a..24115f17de726 100644 --- a/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewBalloon.cpp @@ -325,6 +325,7 @@ void QGIViewBalloon::setGroupSelection(bool isSelected) setSelected(isSelected); balloonLabel->setSelected(isSelected); balloonLines->setSelected(isSelected); + balloonShape->setSelected(isSelected); arrow->setSelected(isSelected); } @@ -377,7 +378,7 @@ void QGIViewBalloon::setViewPartFeature(TechDraw::DrawViewBalloon* balloonFeat) void QGIViewBalloon::updateView(bool update) { - // Base::Console().Message("QGIVB::updateView()\n"); + // Base::Console().Message("QGIVB::updateView()\n"); Q_UNUSED(update); auto balloon(dynamic_cast(getViewObject())); if (!balloon) { @@ -402,7 +403,7 @@ void QGIViewBalloon::updateView(bool update) //update the bubble contents void QGIViewBalloon::updateBalloon(bool obtuse) { - // Base::Console().Message("QGIVB::updateBalloon()\n"); + // Base::Console().Message("QGIVB::updateBalloon()\n"); (void)obtuse; const auto balloon(dynamic_cast(getViewObject())); if (!balloon) { @@ -575,36 +576,27 @@ void QGIViewBalloon::placeBalloon(QPointF pos) void QGIViewBalloon::draw() { - // Base::Console().Message("QGIVB::draw()\n"); + // Base::Console().Message("QGIVB::draw()\n"); // just redirect drawBalloon(false); } void QGIViewBalloon::drawBalloon(bool dragged) { - // Base::Console().Message("QGIVB::drawBalloon(%d)\n", dragged); - if (!isVisible()) { - return; - } + // Base::Console().Message("QGIVB::drawBalloon(%d)\n", dragged); + prepareGeometryChange(); TechDraw::DrawViewBalloon* balloon = dynamic_cast(getViewObject()); - if ((!balloon) ||//nothing to draw, don't try + if ((!balloon) || (!balloon->isDerivedFrom(TechDraw::DrawViewBalloon::getClassTypeId()))) { - balloonLabel->hide(); - hide(); + //nothing to draw, don't try return; } - balloonLabel->show(); - show(); - const TechDraw::DrawView* refObj = balloon->getParentView(); - if (!refObj) { - return; - } - auto vp = static_cast(getViewProvider(getViewObject())); - if (!vp) { + if (!refObj || !vp) { + // can't draw this. probably restoring. return; } @@ -847,6 +839,7 @@ void QGIViewBalloon::drawBalloon(bool dragged) setPrettyNormal(); } + update(); if (parentItem()) { parentItem()->update(); } diff --git a/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.cpp b/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.cpp index 043b67e80c6c5..0375cc23cf8e1 100644 --- a/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.cpp +++ b/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.cpp @@ -25,6 +25,7 @@ #include #include +#include #include "ui_TaskSelectLineAttributes.h" #include "TaskSelectLineAttributes.h" @@ -34,13 +35,6 @@ using namespace Gui; using namespace TechDraw; using namespace TechDrawGui; -//enum class EdgeStyle { -// solid = 1, -// dashed = 2, -// dotted = 3, -// dashdotted = 4 -//}; - enum class EdgeWidth { small = 1, middle = 2, @@ -62,65 +56,6 @@ enum class EdgeColor { // managing global line attributes //=========================================================================== -lineAttributes::lineAttributes() -{ - style = 2; - width = int(EdgeWidth::middle); - color = int(EdgeColor::black); -} - -void lineAttributes::setStyle(int newStyle) -{ - style = newStyle; -} - -void lineAttributes::setWidth(int newWidth) -{ - width = newWidth; -} - -float lineAttributes::getWidthValue() -{ - switch(EdgeWidth(width)) { - case EdgeWidth::small: - return 0.18f; - case EdgeWidth::middle: - return 0.35f; - case EdgeWidth::thick: - return 0.5f; - default: - return 0.35f; - } -} - -void lineAttributes::setColor(int newColor) -{ - color = newColor; -} - -App::Color lineAttributes::getColorValue() -{ - switch (EdgeColor(color)) { - case EdgeColor::black: - return App::Color(0.0f, 0.0f, 0.0f); - case EdgeColor::grey: - return App::Color(0.7f, 0.7f, 0.7f); - case EdgeColor::red: - return App::Color(1.0f, 0.0f, 0.0f); - case EdgeColor::green: - return App::Color(0.0f, 1.0f, 0.0f); - case EdgeColor::blue: - return App::Color(0.0f, 0.0f, 1.0f); - case EdgeColor::magenta: - return App::Color(1.0f, 0.0f, 1.0f); - case EdgeColor::cyan: - return App::Color(0.0f, 1.0f, 1.0f); - case EdgeColor::yellow: - return App::Color(1.0f, 1.0f, 0.0f); - default: - return App::Color(0.0f, 0.0f, 0.0f); - } -} //=========================================================================== // managing global dimension attributes @@ -148,7 +83,7 @@ dimAttributes activeDimAttributes; // container holding dimension attributes // TaskSelectLineAttributes //=========================================================================== -TaskSelectLineAttributes::TaskSelectLineAttributes(lineAttributes * ptActiveAttributes) : +TaskSelectLineAttributes::TaskSelectLineAttributes(LineFormat *ptActiveAttributes) : activeAttributes(ptActiveAttributes), ui(new Ui_TaskSelectLineAttributes) { @@ -189,51 +124,25 @@ void TaskSelectLineAttributes::setUiEdit() ui->cbLineStyle->setCurrentIndex(lineStyle - 1); } - int lineWidth = activeAttributes->getWidth(); - switch(EdgeWidth(lineWidth)) { - case EdgeWidth::small: - ui->rbThin->setChecked(true); - break; - case EdgeWidth::middle: - ui->rbMiddle->setChecked(true); - break; - case EdgeWidth::thick: - ui->rbThick->setChecked(true); - break; - default: - ui->rbMiddle->setChecked(true); - } + // TODO: how to handle translation of a string with arg parameters in it? + ui->rbThin->setText(QString::fromUtf8("Thin %1").arg(QString::number(TechDraw::LineGroup::getDefaultWidth("Thin")))); + ui->rbMiddle->setText(QString::fromUtf8("Middle %1").arg(QString::number(TechDraw::LineGroup::getDefaultWidth("Graphic")))); + ui->rbThick->setText(QString::fromUtf8("Thick %1").arg(QString::number(TechDraw::LineGroup::getDefaultWidth("Thick")))); - int lineColor = activeAttributes->getColor(); - switch(EdgeColor(lineColor)) { - case EdgeColor::black: - ui->rbBlack->setChecked(true); - break; - case EdgeColor::grey: - ui->rbGrey->setChecked(true); - break; - case EdgeColor::red: - ui->rbRed->setChecked(true); - break; - case EdgeColor::green: - ui->rbGreen->setChecked(true); - break; - case EdgeColor::blue: - ui->rbBlue->setChecked(true); - break; - case EdgeColor::magenta: - ui->rbMagenta->setChecked(true); - break; - case EdgeColor::cyan: - ui->rbCyan->setChecked(true); - break; - case EdgeColor::yellow: - ui->rbYellow->setChecked(true); - break; - default: - ui->rbBlack->setChecked(true); + double lineWidth = activeAttributes->getWidth(); + if (lineWidth <= TechDraw::LineGroup::getDefaultWidth("Thin")) { + ui->rbThin->setChecked(true); + } else if (lineWidth <= TechDraw::LineGroup::getDefaultWidth("Graphic")) { + ui->rbMiddle->setChecked(true); + } else if (lineWidth <= TechDraw::LineGroup::getDefaultWidth("Thick")) { + ui->rbThick->setChecked(true); + } else { + ui->rbMiddle->setChecked(true); } + QColor lineColor = activeAttributes->getQColor(); + ui->cbColor->setColor(lineColor); + double cascadeSpacing = activeDimAttributes.getCascadeSpacing(); ui->sbSpacing->setValue(cascadeSpacing); double lineStretching = activeDimAttributes.getLineStretch(); @@ -244,47 +153,25 @@ void TaskSelectLineAttributes::setUiEdit() bool TaskSelectLineAttributes::accept() { activeAttributes->setStyle(ui->cbLineStyle->currentIndex() + 1); + activeAttributes->setLineNumber(ui->cbLineStyle->currentIndex() + 1); if (ui->rbThin->isChecked()){ - activeAttributes->setWidth(int(EdgeWidth::small)); + activeAttributes->setWidth(TechDraw::LineGroup::getDefaultWidth("Thin")); } else if (ui->rbMiddle->isChecked()){ - activeAttributes->setWidth(int(EdgeWidth::middle)); + activeAttributes->setWidth(TechDraw::LineGroup::getDefaultWidth("Graphic")); } else if (ui->rbThick->isChecked()){ - activeAttributes->setWidth(int(EdgeWidth::thick)); + activeAttributes->setWidth(TechDraw::LineGroup::getDefaultWidth("Thick")); } else { - activeAttributes->setWidth(int(EdgeWidth::middle)); + activeAttributes->setWidth(TechDraw::LineGroup::getDefaultWidth("Graphic")); } - if (ui->rbBlack->isChecked()){ - activeAttributes->setColor(int(EdgeColor::black)); - } - else if (ui->rbGrey->isChecked()){ - activeAttributes->setColor(int(EdgeColor::grey)); - } - else if (ui->rbRed->isChecked()){ - activeAttributes->setColor(int(EdgeColor::red)); - } - else if (ui->rbGreen->isChecked()){ - activeAttributes->setColor(int(EdgeColor::green)); - } - else if (ui->rbBlue->isChecked()){ - activeAttributes->setColor(int(EdgeColor::blue)); - } - else if (ui->rbMagenta->isChecked()){ - activeAttributes->setColor(int(EdgeColor::magenta)); - } - else if (ui->rbCyan->isChecked()){ - activeAttributes->setColor(int(EdgeColor::cyan)); - } - else if (ui->rbYellow->isChecked()){ - activeAttributes->setColor(int(EdgeColor::yellow)); - } - else { - activeAttributes->setColor(int(EdgeColor::black)); - } + QColor qTemp = ui->cbColor->color(); + App::Color temp; + temp.set(qTemp.redF(), qTemp.greenF(), qTemp.blueF(), 1.0 - qTemp.alphaF()); + activeAttributes->setColor(temp); double cascadeSpacing = ui->sbSpacing->value(); activeDimAttributes.setCascadeSpacing(cascadeSpacing); @@ -303,7 +190,7 @@ bool TaskSelectLineAttributes::reject() // TaskDlgSelectLineAttributes //=========================================================================== -TaskDlgSelectLineAttributes::TaskDlgSelectLineAttributes(lineAttributes * ptActiveAttributes) +TaskDlgSelectLineAttributes::TaskDlgSelectLineAttributes(LineFormat *ptActiveAttributes) : TaskDialog() { widget = new TaskSelectLineAttributes(ptActiveAttributes); diff --git a/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.h b/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.h index 161a0891ffeb3..41744297fd7d8 100644 --- a/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.h +++ b/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.h @@ -26,6 +26,7 @@ #include #include #include +#include class dimAttributes { @@ -69,31 +70,31 @@ class MDIViewPage; class ViewProviderViewPart; class Ui_TaskSelectLineAttributes; -class lineAttributes { - int style; - int width; - int color; +// class lineAttributes { +// int style; +// int width; +// int color; -public: +// public: - lineAttributes(); - void setStyle(int); - int getStyle() const {return style;} - void setWidth(int); - int getWidth() const {return width;} - float getWidthValue(); - void setColor(int); - int getColor() const {return color;} - App::Color getColorValue(); +// lineAttributes(); +// void setStyle(int); +// int getStyle() const {return style;} +// void setWidth(int); +// int getWidth() const {return width;} +// float getWidthValue(); +// void setColor(int); +// int getColor() const {return color;} +// App::Color getColorValue(); -}; // class lineAttributes +// }; // class lineAttributes class TaskSelectLineAttributes : public QWidget { Q_OBJECT public: - explicit TaskSelectLineAttributes(lineAttributes * ptActiveAttributes); + explicit TaskSelectLineAttributes(TechDraw::LineFormat* ptActiveAttributes); ~TaskSelectLineAttributes() override; virtual bool accept(); @@ -106,7 +107,8 @@ class TaskSelectLineAttributes : public QWidget void setUiEdit(); private: - lineAttributes* activeAttributes; + // lineAttributes* activeAttributes; + TechDraw::LineFormat* activeAttributes; std::unique_ptr ui; TechDraw::LineGenerator* m_lineGenerator; @@ -117,7 +119,7 @@ class TaskDlgSelectLineAttributes : public Gui::TaskView::TaskDialog Q_OBJECT public: - explicit TaskDlgSelectLineAttributes(lineAttributes * ptActiveAttributes); + explicit TaskDlgSelectLineAttributes(TechDraw::LineFormat * ptActiveAttributes); ~TaskDlgSelectLineAttributes() override; public: diff --git a/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.ui b/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.ui index 9e30a0574f63d..57eef4ca3ee50 100644 --- a/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.ui +++ b/src/Mod/TechDraw/Gui/TaskSelectLineAttributes.ui @@ -6,8 +6,8 @@ 0 0 - 332 - 401 + 424 + 308 @@ -95,6 +95,9 @@ + + + @@ -102,113 +105,6 @@ - - - - Black - - - true - - - true - - - bgLineColor - - - - - - - Blue - - - true - - - bgLineColor - - - - - - - Grey - - - true - - - bgLineColor - - - - - - - Magenta - - - true - - - bgLineColor - - - - - - - Red - - - true - - - bgLineColor - - - - - - - Cyan - - - true - - - bgLineColor - - - - - - - Green - - - true - - - bgLineColor - - - - - - - Yellow - - - true - - - bgLineColor - - - @@ -267,6 +163,18 @@ + + + Gui::ColorButton + QPushButton +
Gui/Widgets.h
+
+ + Gui::PrefColorButton + Gui::ColorButton +
Gui/PrefWidgets.h
+
+
diff --git a/src/Mod/TechDraw/Gui/ViewProviderDrawingViewExtension.cpp b/src/Mod/TechDraw/Gui/ViewProviderDrawingViewExtension.cpp index 991ca34d12a2a..18112b968f4f4 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderDrawingViewExtension.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderDrawingViewExtension.cpp @@ -58,9 +58,24 @@ void ViewProviderDrawingViewExtension::extensionDragObject(App::DocumentObject* //extension to try to drop on us and cause problems bool ViewProviderDrawingViewExtension::extensionCanDropObjects() const { return true; } -//let the page have any drops we receive +//let the page have any drops we receive. bool ViewProviderDrawingViewExtension::extensionCanDropObject(App::DocumentObject* obj) const { + // it can happen that if the tree gets badly corrupted, there can be loose + // objects that have no page or view provider, so we need to check that + // all these objects exist. + auto vpdv = getViewProviderDrawingView(); + if (!vpdv) { + return false; + } + auto vpp = vpdv->getViewProviderPage(); + if (!vpp) { + return false; + } + auto vppEx = vpp->getVPPExtension(); + if (!vppEx) { + return false; + } return getViewProviderDrawingView() ->getViewProviderPage() ->getVPPExtension() diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index f8169423c6586..ca7e8e69b4f92 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 0000000000000..b3faa3ca2db7f --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeCache.cpp @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" +#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; +}; + +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)