diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 0263b06d3a..2ba595cd13 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -37,11 +37,17 @@ #include #include #include -#include #include #include +// FIXME? +// including instead of the incomplete class declaration +// below results in a broken link on Windows but builds on other platforms. I suspect +// that's why these class declarations appear in the RT branch. +// Something about how that compiler is mangling the names? Maybe we're missing a +// magic windows specific qualifier on the declarations. +class BRepPrimAPI_MakeHalfSpace; class gp_Ax1; class gp_Ax2; class gp_Pln; @@ -56,6 +62,7 @@ namespace Part { class TopoShapeCache; +class TopoShape; /* A special sub-class to indicate null shapes */ diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index c5a122e95e..82145db6b3 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -39,6 +39,7 @@ #include "TopoShapeCache.h" #include "FaceMaker.h" +#include "TopoShapeOpCode.h" FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -551,8 +552,6 @@ TopoShape::makeElementCompound(const std::vector& shapes, const char* return *this; } -<<<<<<< HEAD -======= struct MapperSewing: Part::TopoShape::Mapper { BRepBuilderAPI_Sewing& maker; @@ -690,7 +689,6 @@ TopoShape& TopoShape::makeElementShape(BRepPrimAPI_MakeHalfSpace& mkShape, return makeShapeWithElementMap(mkShape.Solid(), MapperMaker(mkShape), {source}, op); } ->>>>>>> ad521d6a23 (Missing method) TopoShape& TopoShape::makeElementFace(const TopoShape& shape, const char* op, const char* maker, @@ -950,117 +948,104 @@ TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, return makeShapeWithElementMap(mkShape.Shape(), MapperMaker(mkShape), shapes, op); } -TopoShape &TopoShape::makeElementShape(BRepOffsetAPI_ThruSections &mk, const TopoShape &source, - const char *op) +TopoShape& +TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk, const TopoShape& source, const char* op) { - if(!op) op = Part::OpCodes::ThruSections; - return makeElementShape(mk,std::vector(1,source),op); + if (!op) { + op = Part::OpCodes::ThruSections; + } + return makeElementShape(mk, std::vector(1, source), op); } -TopoShape &TopoShape::makeElementShape(BRepOffsetAPI_ThruSections &mk, const std::vector &sources, - const char *op) +TopoShape& TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk, + const std::vector& sources, + const char* op) { - if(!op) op = Part::OpCodes::ThruSections; - return makeShapeWithElementMap(mk.Shape(),MapperThruSections(mk,sources),sources,op); + if (!op) { + op = Part::OpCodes::ThruSections; + } + return makeShapeWithElementMap(mk.Shape(), MapperThruSections(mk, sources), sources, op); } -TopoShape &TopoShape::makeElementShape(BRepBuilderAPI_Sewing &mk, const std::vector &shapes, - const char *op) +TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mk, + const std::vector& shapes, + const char* op) { - if(!op) op = Part::OpCodes::Sewing; - return makeShapeWithElementMap(mk.SewedShape(),MapperSewing(mk),shapes,op); + if (!op) { + op = Part::OpCodes::Sewing; + } + return makeShapeWithElementMap(mk.SewedShape(), MapperSewing(mk), shapes, op); } -TopoShape &TopoShape::makeElementShape(BRepBuilderAPI_Sewing &mkShape, - const TopoShape &source, const char *op) +TopoShape& +TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mkShape, const TopoShape& source, const char* op) { - if(!op) op = Part::OpCodes::Sewing; - return makeElementShape(mkShape,std::vector(1,source),op); + if (!op) { + op = Part::OpCodes::Sewing; + } + return makeElementShape(mkShape, std::vector(1, source), op); } -struct MapperSewing: Part::TopoShape::Mapper { - BRepBuilderAPI_Sewing &maker; - MapperSewing(BRepBuilderAPI_Sewing &maker) - :maker(maker) +struct MapperSewing: Part::TopoShape::Mapper +{ + BRepBuilderAPI_Sewing& maker; + MapperSewing(BRepBuilderAPI_Sewing& maker) + : maker(maker) {} - virtual const std::vector &modified(const TopoDS_Shape &s) const override { + virtual const std::vector& modified(const TopoDS_Shape& s) const override + { _res.clear(); try { - const auto &shape = maker.Modified(s); - if(!shape.IsNull() && !shape.IsSame(s)) + const auto& shape = maker.Modified(s); + if (!shape.IsNull() && !shape.IsSame(s)) { _res.push_back(shape); - else { - const auto &sshape = maker.ModifiedSubShape(s); - if(!sshape.IsNull() && !sshape.IsSame(s)) - _res.push_back(sshape); } - } catch (const Standard_Failure & e) { - if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + else { + const auto& sshape = maker.ModifiedSubShape(s); + if (!sshape.IsNull() && !sshape.IsSame(s)) { + _res.push_back(sshape); + } + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } } return _res; } }; -struct MapperThruSections: MapperMaker { - TopoShape firstProfile; - TopoShape lastProfile; - - MapperThruSections(BRepOffsetAPI_ThruSections &tmaker, - const std::vector &profiles) - :MapperMaker(tmaker) - { - if(!tmaker.FirstShape().IsNull()) - firstProfile = profiles.front(); - if(!tmaker.LastShape().IsNull()) - lastProfile = profiles.back(); - } - virtual const std::vector &generated(const TopoDS_Shape &s) const override { - MapperMaker::generated(s); - if(_res.size()) return _res; - try { - auto &tmaker = static_cast(maker); - auto shape = tmaker.GeneratedFace(s); - if(!shape.IsNull()) - _res.push_back(shape); - if(firstProfile.getShape().IsSame(s) || firstProfile.findShape(s)) - _res.push_back(tmaker.FirstShape()); - else if(lastProfile.getShape().IsSame(s) || lastProfile.findShape(s)) - _res.push_back(tmaker.LastShape()); - } catch (const Standard_Failure & e) { - if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) - FC_WARN("Exception on shape mapper: " << e.GetMessageString()); - } - return _res; - } -}; - -const std::vector & -MapperMaker::modified(const TopoDS_Shape &s) const +const std::vector& MapperMaker::modified(const TopoDS_Shape& s) const { _res.clear(); try { TopTools_ListIteratorOfListOfShape it; - for (it.Initialize(maker.Modified(s)); it.More(); it.Next()) + for (it.Initialize(maker.Modified(s)); it.More(); it.Next()) { _res.push_back(it.Value()); - } catch (const Standard_Failure & e) { - if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } } return _res; } -const std::vector & -MapperMaker::generated(const TopoDS_Shape &s) const +const std::vector& MapperMaker::generated(const TopoDS_Shape& s) const { _res.clear(); try { TopTools_ListIteratorOfListOfShape it; - for (it.Initialize(maker.Generated(s)); it.More(); it.Next()) + for (it.Initialize(maker.Generated(s)); it.More(); it.Next()) { _res.push_back(it.Value()); - } catch (const Standard_Failure & e) { - if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } } return _res; } diff --git a/src/Mod/Part/App/TopoShapeExpansion.sync-conflict-20240128-110016-QVQC5GY.cpp b/src/Mod/Part/App/TopoShapeExpansion.sync-conflict-20240128-110016-QVQC5GY.cpp new file mode 100644 index 0000000000..e96233cfbe --- /dev/null +++ b/src/Mod/Part/App/TopoShapeExpansion.sync-conflict-20240128-110016-QVQC5GY.cpp @@ -0,0 +1,1096 @@ +// 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" +#ifndef _PreComp_ +#include +#include +#include +#include + +#include +#include + +#endif + +#include "TopoShape.h" +#include "TopoShapeCache.h" +#include "FaceMaker.h" + +#include "TopoShapeOpCode.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; +} + +// The following lines should be used for now to replace the original macros (in the future we can +// refactor to use std::source_location and eliminate the use of the macros entirely). +// FC_THROWM(NullShapeException, "Null shape"); +// FC_THROWM(NullShapeException, "Null input shape"); +// FC_WARN("Null input shape"); // NOLINT +// +// The original macros: +// #define HANDLE_NULL_SHAPE _HANDLE_NULL_SHAPE("Null shape",true) +// #define HANDLE_NULL_INPUT _HANDLE_NULL_SHAPE("Null input shape",true) +// #define WARN_NULL_INPUT _HANDLE_NULL_SHAPE("Null input shape",false) + +bool TopoShape::hasPendingElementMap() const +{ + return !elementMap(false) && this->_cache + && (this->_parentCache || this->_cache->cachedElementMap); +} + +bool TopoShape::canMapElement(const TopoShape& other) const +{ + if (isNull() || other.isNull() || this == &other || other.Tag == -1 || Tag == -1) { + return false; + } + if ((other.Tag == 0) && !other.elementMap(false) && !other.hasPendingElementMap()) { + return false; + } + initCache(); + other.initCache(); + _cache->relations.clear(); + return true; +} + +namespace +{ +size_t checkSubshapeCount(const TopoShape& topoShape1, + const TopoShape& topoShape2, + TopAbs_ShapeEnum elementType) +{ + auto count = topoShape1.countSubShapes(elementType); + auto other = topoShape2.countSubShapes(elementType); + if (count != other) { + FC_WARN("sub shape mismatch"); // NOLINT + if (count > other) { + count = other; + } + } + return count; +} +} // namespace + +void TopoShape::setupChild(Data::ElementMap::MappedChildElements& child, + TopAbs_ShapeEnum elementType, + const TopoShape& topoShape, + size_t shapeCount, + const char* op) +{ + child.indexedName = Data::IndexedName::fromConst(TopoShape::shapeName(elementType).c_str(), 1); + child.offset = 0; + child.count = static_cast(shapeCount); + child.elementMap = topoShape.elementMap(); + if (this->Tag != topoShape.Tag) { + child.tag = topoShape.Tag; + } + else { + child.tag = 0; + } + if (op) { + child.postfix = op; + } +} + +void TopoShape::copyElementMap(const TopoShape& topoShape, const char* op) +{ + if (topoShape.isNull() || isNull()) { + return; + } + std::vector children; + std::array elementTypes = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; + for (const auto elementType : elementTypes) { + auto count = checkSubshapeCount(*this, topoShape, elementType); + if (count == 0) { + continue; + } + children.emplace_back(); + auto& child = children.back(); + setupChild(child, elementType, topoShape, count, op); + } + resetElementMap(); + if (!Hasher) { + Hasher = topoShape.Hasher; + } + setMappedChildElements(children); +} + +namespace +{ +void warnIfLogging() +{ + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("hasher mismatch"); // NOLINT + } +}; + +void hasherMismatchError() +{ + FC_ERR("hasher mismatch"); // NOLINT +} + + +void checkAndMatchHasher(TopoShape& topoShape1, const TopoShape& topoShape2) +{ + if (topoShape1.Hasher) { + if (topoShape2.Hasher != topoShape1.Hasher) { + if (topoShape1.getElementMapSize(false) == 0U) { + warnIfLogging(); + } + else { + hasherMismatchError(); + } + topoShape1.Hasher = topoShape2.Hasher; + } + } + else { + topoShape1.Hasher = topoShape2.Hasher; + } +} +} // namespace + +void TopoShape::mapSubElementTypeForShape(const TopoShape& other, + TopAbs_ShapeEnum type, + const char* op, + int count, + bool forward, + bool& warned) +{ + auto& shapeMap = _cache->getAncestry(type); + auto& otherMap = other._cache->getAncestry(type); + const char* shapeType = shapeName(type).c_str(); + + // 1-indexed for readability (e.g. there is no "Edge0", we started at "Edge1", etc.) + for (int outerCounter = 1; outerCounter <= count; ++outerCounter) { + int innerCounter {0}; + int index {0}; + if (forward) { + innerCounter = outerCounter; + index = shapeMap.find(_Shape, otherMap.find(other._Shape, outerCounter)); + if (index == 0) { + continue; + } + } + else { + index = outerCounter; + innerCounter = otherMap.find(other._Shape, shapeMap.find(_Shape, outerCounter)); + if (innerCounter == 0) { + continue; + } + } + Data::IndexedName element = Data::IndexedName::fromConst(shapeType, index); + for (auto& mappedName : + other.getElementMappedNames(Data::IndexedName::fromConst(shapeType, innerCounter), + true)) { + auto& name = mappedName.first; + auto& sids = mappedName.second; + if (!sids.empty()) { + if (!Hasher) { + Hasher = sids[0].getHasher(); + } + else if (!sids[0].isFromSameHasher(Hasher)) { + if (!warned) { + warned = true; + FC_WARN("hasher mismatch"); // NOLINT + } + sids.clear(); + } + } + std::ostringstream ss; + char elementType {shapeName(type)[0]}; + if ( ! elementMap() ) { + FC_THROWM(NullShapeException, "No element map"); + } + elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag); + elementMap()->setElementName(element, name, Tag, &sids); + } + } +} + +void TopoShape::mapSubElementForShape(const TopoShape& other, const char* op) +{ + bool warned = false; + static const std::array types = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; + + for (auto type : types) { + auto& shapeMap = _cache->getAncestry(type); + auto& otherMap = other._cache->getAncestry(type); + if ((shapeMap.count() == 0) || (otherMap.count() == 0)) { + continue; + } + + bool forward {false}; + int count {0}; + if (otherMap.count() <= shapeMap.count()) { + forward = true; + count = otherMap.count(); + } + else { + forward = false; + count = shapeMap.count(); + } + mapSubElementTypeForShape(other, type, op, count, forward, warned); + } +} + +void TopoShape::mapSubElement(const TopoShape& other, const char* op, bool forceHasher) +{ + if (!canMapElement(other)) { + return; + } + + if ((getElementMapSize(false) == 0U) && this->_Shape.IsPartner(other._Shape)) { + if (!this->Hasher) { + this->Hasher = other.Hasher; + } + copyElementMap(other, op); + return; + } + + if (!forceHasher && other.Hasher) { + checkAndMatchHasher(*this, other); + } + + mapSubElementForShape(other, op); +} + +std::vector +TopoShape::createChildMap(size_t count, const std::vector& shapes, const char* op) +{ + std::vector children; + children.reserve(count * (size_t)3); + std::array types = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; + for (const auto topAbsType : types) { + size_t offset = 0; + for (auto& topoShape : shapes) { + if (topoShape.isNull()) { + continue; + } + auto subShapeCount = topoShape.countSubShapes(topAbsType); + if (subShapeCount == 0) { + continue; + } + children.emplace_back(); + auto& child = children.back(); + child.indexedName = + Data::IndexedName::fromConst(TopoShape::shapeName(topAbsType).c_str(), 1); + child.offset = static_cast(offset); + offset += subShapeCount; + child.count = static_cast(subShapeCount); + child.elementMap = topoShape.elementMap(); + child.tag = topoShape.Tag; + if (op) { + child.postfix = op; + } + } + } + return children; +} + +void TopoShape::mapCompoundSubElements(const std::vector& shapes, const char* op) +{ + int count = 0; + for (auto& topoShape : shapes) { + if (topoShape.isNull()) { + continue; + } + ++count; + auto subshape = getSubShape(TopAbs_SHAPE, count, /*silent = */ true); + if (!subshape.IsPartner(topoShape._Shape)) { + return; // Not a partner shape, don't do any mapping at all + } + } + auto children {createChildMap(count, shapes, op)}; + setMappedChildElements(children); +} + +void TopoShape::mapSubElement(const std::vector& shapes, const char* op) +{ + if (shapes.empty()) { + return; + } + + if (shapeType(true) == TopAbs_COMPOUND) { + mapCompoundSubElements(shapes, op); + } + else { + for (auto& shape : shapes) { + mapSubElement(shape, op); + } + } +} + +namespace +{ +void addShapesToBuilder(const std::vector& shapes, + BRep_Builder& builder, + TopoDS_Compound& comp) +{ + int count = 0; + for (auto& topoShape : shapes) { + if (topoShape.isNull()) { + FC_WARN("Null input shape"); // NOLINT + continue; + } + builder.Add(comp, topoShape.getShape()); + ++count; + } + if (count == 0) { + FC_THROWM(NullShapeException, "Null shape"); + } +} +} // namespace + +TopoShape& +TopoShape::makeElementCompound(const std::vector& shapes, const char* op, bool force) +{ + if (!force && shapes.size() == 1) { + *this = shapes[0]; + return *this; + } + + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + + if (shapes.empty()) { + setShape(comp); + return *this; + } + addShapesToBuilder(shapes, builder, comp); + setShape(comp); + initCache(); + + mapSubElement(shapes, op); + return *this; +} + +struct MapperSewing: Part::TopoShape::Mapper +{ + BRepBuilderAPI_Sewing& maker; + MapperSewing(BRepBuilderAPI_Sewing& maker) + : maker(maker) + {} + virtual const std::vector& modified(const TopoDS_Shape& s) const override + { + _res.clear(); + try { + const auto& shape = maker.Modified(s); + if (!shape.IsNull() && !shape.IsSame(s)) { + _res.push_back(shape); + } + else { + const auto& sshape = maker.ModifiedSubShape(s); + if (!sshape.IsNull() && !sshape.IsSame(s)) { + _res.push_back(sshape); + } + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; + } +}; + +struct MapperThruSections: MapperMaker +{ + TopoShape firstProfile; + TopoShape lastProfile; + + MapperThruSections(BRepOffsetAPI_ThruSections& tmaker, const std::vector& profiles) + : MapperMaker(tmaker) + { + if (!tmaker.FirstShape().IsNull()) { + firstProfile = profiles.front(); + } + if (!tmaker.LastShape().IsNull()) { + lastProfile = profiles.back(); + } + } + virtual const std::vector& generated(const TopoDS_Shape& s) const override + { + MapperMaker::generated(s); + if (_res.size()) { + return _res; + } + try { + auto& tmaker = static_cast(maker); + auto shape = tmaker.GeneratedFace(s); + if (!shape.IsNull()) { + _res.push_back(shape); + } + if (firstProfile.getShape().IsSame(s) || firstProfile.findShape(s)) { + _res.push_back(tmaker.FirstShape()); + } + else if (lastProfile.getShape().IsSame(s) || lastProfile.findShape(s)) { + _res.push_back(tmaker.LastShape()); + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; + } +}; + + +TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, + const TopoShape& source, + const char* op) +{ + std::vector sources(1, source); + return makeElementShape(mkShape, sources, op); +} + +TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, + const std::vector& shapes, + const char* op) +{ + return makeShapeWithElementMap(mkShape.Shape(), MapperMaker(mkShape), shapes, op); +} + +TopoShape& +TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk, const TopoShape& source, const char* op) +{ + if (!op) { + op = Part::OpCodes::ThruSections; + } + return makeElementShape(mk, std::vector(1, source), op); +} + +TopoShape& TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk, + const std::vector& sources, + const char* op) +{ + if (!op) { + op = Part::OpCodes::ThruSections; + } + return makeShapeWithElementMap(mk.Shape(), MapperThruSections(mk, sources), sources, op); +} + +TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mk, + const std::vector& shapes, + const char* op) +{ + if (!op) { + op = Part::OpCodes::Sewing; + } + return makeShapeWithElementMap(mk.SewedShape(), MapperSewing(mk), shapes, op); +} + +TopoShape& +TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mkShape, const TopoShape& source, const char* op) +{ + if (!op) { + op = Part::OpCodes::Sewing; + } + return makeElementShape(mkShape, std::vector(1, source), op); +} + +TopoShape& TopoShape::makeElementShape(BRepPrimAPI_MakeHalfSpace& mkShape, + const TopoShape& source, + const char* op) +{ + if (!op) { + op = Part::OpCodes::HalfSpace; + } + return makeShapeWithElementMap(mkShape.Solid(), MapperMaker(mkShape), {source}, op); +} + +TopoShape& TopoShape::makeElementFace(const TopoShape& shape, + const char* op, + const char* maker, + const gp_Pln* plane) +{ + std::vector shapes; + if (shape.isNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + if (shape.getShape().ShapeType() == TopAbs_COMPOUND) { + shapes = shape.getSubTopoShapes(); + } + else { + shapes.push_back(shape); + } + return makeElementFace(shapes, op, maker, plane); +} + +TopoShape& TopoShape::makeElementFace(const std::vector& shapes, + const char* op, + const char* maker, + const gp_Pln* plane) +{ + if (!maker || !maker[0]) { + maker = "Part::FaceMakerBullseye"; + } + std::unique_ptr mkFace = FaceMaker::ConstructFromType(maker); + mkFace->MyHasher = Hasher; + mkFace->MyOp = op; + if (plane) { + mkFace->setPlane(*plane); + } + + for (auto& shape : shapes) { + if (shape.getShape().ShapeType() == TopAbs_COMPOUND) { + mkFace->useTopoCompound(shape); + } + else { + mkFace->addTopoShape(shape); + } + } + mkFace->Build(); + + const auto& ret = mkFace->getTopoShape(); + setShape(ret._Shape); + Hasher = ret.Hasher; + resetElementMap(ret.elementMap()); + if (!isValid()) { + ShapeFix_ShapeTolerance aSFT; + aSFT.LimitTolerance(getShape(), + Precision::Confusion(), + Precision::Confusion(), + TopAbs_SHAPE); + + // In some cases, the OCC reports the returned shape having invalid + // tolerance. Not sure about the real cause. + // + // Update: one of the cause is related to OCC bug in + // BRepBuilder_FindPlane, A possible call sequence is, + // + // makEOffset2D() -> TopoShape::findPlane() -> BRepLib_FindSurface + // + // See code comments in findPlane() for the description of the bug and + // work around. + + ShapeFix_Shape fixer(getShape()); + fixer.Perform(); + setShape(fixer.Shape(), false); + + if (!isValid()) { + FC_WARN("makeElementFace: resulting face is invalid"); + } + } + return *this; +} + +/** + * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. + * + * @param element The element name(type) that provides 1 one character suffix to the name IF . + * @param names The subnames to build the name from. If empty, return the TopoShape MappedName. + * @param marker The elementMap name or suffix to start the name with. If null, use the + * elementMapPrefix. + * @param op The op text passed to the element name encoder along with the TopoShape Tag + * @param _sids If defined, records the sub ids processed. + * + * @return The encoded, possibly hashed name. + */ +Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element, + const std::vector& names, + const char* marker, + const char* op, + const Data::ElementIDRefs* _sids) +{ + if (names.empty()) { + return Data::MappedName(); + } + std::string _marker; + if (!marker) { + marker = elementMapPrefix().c_str(); + } + else if (!boost::starts_with(marker, elementMapPrefix())) { + _marker = elementMapPrefix() + marker; + marker = _marker.c_str(); + } + auto it = names.begin(); + Data::MappedName newName = *it; + std::ostringstream ss; + Data::ElementIDRefs sids; + if (_sids) { + sids = *_sids; + } + if (names.size() == 1) { + ss << marker; + } + else { + bool first = true; + ss.str(""); + if (!Hasher) { + ss << marker; + } + ss << '('; + for (++it; it != names.end(); ++it) { + if (first) { + first = false; + } + else { + ss << '|'; + } + ss << *it; + } + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << marker << sids.back().toString(); + } + } + elementMap()->encodeElementName(element[0], newName, ss, &sids, Tag, op); + return elementMap()->setElementName(element, newName, Tag, &sids); +} + +/** + * Reorient the outer and inner wires of the TopoShape + * + * @param inner If this is not a nullptr, then any inner wires processed will be returned in this + * vector. + * @param reorient One of NoReorient, Reorient ( Outer forward, inner reversed ), + * ReorientForward ( all forward ), or ReorientReversed ( all reversed ) + * @return The outer wire, or an empty TopoShape if this isn't a Face, has no Face subShapes, or the + * outer wire isn't found. + */ +TopoShape TopoShape::splitWires(std::vector* inner, SplitWireReorient reorient) const +{ + // ShapeAnalysis::OuterWire() is un-reliable for some reason. OCC source + // code shows it works by creating face using each wire, and then test using + // BRepTopAdaptor_FClass2d::PerformInfinitePoint() to check if it is an out + // bound wire. And practice shows it sometimes returns the incorrect + // result. Need more investigation. Note that this may be related to + // unreliable solid face orientation + // (https://forum.freecadweb.org/viewtopic.php?p=446006#p445674) + // + // Use BrepTools::OuterWire() instead. OCC source code shows it is + // implemented using simple bound box checking. This should be a + // reliable method, especially so for a planar face. + + TopoDS_Shape tmp; + if (shapeType(true) == TopAbs_FACE) { + tmp = BRepTools::OuterWire(TopoDS::Face(_Shape)); + } + else if (countSubShapes(TopAbs_FACE) == 1) { + tmp = BRepTools::OuterWire(TopoDS::Face(getSubShape(TopAbs_FACE, 1))); + } + if (tmp.IsNull()) { + return TopoShape(); + } + const auto& wires = getSubTopoShapes(TopAbs_WIRE); + auto it = wires.begin(); + + TopAbs_Orientation orientOuter, orientInner; + switch (reorient) { + case ReorientReversed: + orientOuter = orientInner = TopAbs_REVERSED; + break; + case ReorientForward: + orientOuter = orientInner = TopAbs_FORWARD; + break; + default: + orientOuter = TopAbs_FORWARD; + orientInner = TopAbs_REVERSED; + break; + } + + auto doReorient = [](TopoShape& s, TopAbs_Orientation orient) { + // Special case of single edge wire. Make sure the edge is in the + // required orientation. This is necessary because BRepFill_OffsetWire + // has special handling of circular edge offset, which seem to only + // respect the edge orientation and disregard the wire orientation. The + // orientation is used to determine whether to shrink or expand. + if (s.countSubShapes(TopAbs_EDGE) == 1) { + TopoDS_Shape e = s.getSubShape(TopAbs_EDGE, 1); + if (e.Orientation() == orient) { + if (s._Shape.Orientation() == orient) { + return; + } + } + else { + e = e.Oriented(orient); + } + BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(e)); + s.setShape(mkWire.Shape(), false); + } + else if (s._Shape.Orientation() != orient) { + s.setShape(s._Shape.Oriented(orient), false); + } + }; + + for (; it != wires.end(); ++it) { + auto& wire = *it; + if (wire.getShape().IsSame(tmp)) { + if (inner) { + for (++it; it != wires.end(); ++it) { + inner->push_back(*it); + if (reorient) { + doReorient(inner->back(), orientInner); + } + } + } + auto res = wire; + if (reorient) { + doReorient(res, orientOuter); + } + return res; + } + if (inner) { + inner->push_back(wire); + if (reorient) { + doReorient(inner->back(), orientInner); + } + } + } + return TopoShape(); +} + +TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, + const TopoShape& source, + const char* op) +{ + std::vector sources(1, source); + return makeElementShape(mkShape, sources, op); +} + +TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, + const std::vector& shapes, + const char* op) +{ + return makeShapeWithElementMap(mkShape.Shape(), MapperMaker(mkShape), shapes, op); +} + +TopoShape& +TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk, const TopoShape& source, const char* op) +{ + if (!op) { + op = Part::OpCodes::ThruSections; + } + return makeElementShape(mk, std::vector(1, source), op); +} + +TopoShape& TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk, + const std::vector& sources, + const char* op) +{ + if (!op) { + op = Part::OpCodes::ThruSections; + } + return makeShapeWithElementMap(mk.Shape(), MapperThruSections(mk, sources), sources, op); +} + +TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mk, + const std::vector& shapes, + const char* op) +{ + if (!op) { + op = Part::OpCodes::Sewing; + } + return makeShapeWithElementMap(mk.SewedShape(), MapperSewing(mk), shapes, op); +} + +TopoShape& +TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mkShape, const TopoShape& source, const char* op) +{ + if (!op) { + op = Part::OpCodes::Sewing; + } + return makeElementShape(mkShape, std::vector(1, source), op); +} + +struct MapperSewing: Part::TopoShape::Mapper +{ + BRepBuilderAPI_Sewing& maker; + MapperSewing(BRepBuilderAPI_Sewing& maker) + : maker(maker) + {} + virtual const std::vector& modified(const TopoDS_Shape& s) const override + { + _res.clear(); + try { + const auto& shape = maker.Modified(s); + if (!shape.IsNull() && !shape.IsSame(s)) { + _res.push_back(shape); + } + else { + const auto& sshape = maker.ModifiedSubShape(s); + if (!sshape.IsNull() && !sshape.IsSame(s)) { + _res.push_back(sshape); + } + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; + } +}; + +struct MapperThruSections: MapperMaker +{ + TopoShape firstProfile; + TopoShape lastProfile; + + MapperThruSections(BRepOffsetAPI_ThruSections& tmaker, const std::vector& profiles) + : MapperMaker(tmaker) + { + if (!tmaker.FirstShape().IsNull()) { + firstProfile = profiles.front(); + } + if (!tmaker.LastShape().IsNull()) { + lastProfile = profiles.back(); + } + } + virtual const std::vector& generated(const TopoDS_Shape& s) const override + { + MapperMaker::generated(s); + if (_res.size()) { + return _res; + } + try { + auto& tmaker = static_cast(maker); + auto shape = tmaker.GeneratedFace(s); + if (!shape.IsNull()) { + _res.push_back(shape); + } + if (firstProfile.getShape().IsSame(s) || firstProfile.findShape(s)) { + _res.push_back(tmaker.FirstShape()); + } + else if (lastProfile.getShape().IsSame(s) || lastProfile.findShape(s)) { + _res.push_back(tmaker.LastShape()); + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; + } +}; + +const std::vector& MapperMaker::modified(const TopoDS_Shape& s) const +{ + _res.clear(); + try { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(maker.Modified(s)); it.More(); it.Next()) { + _res.push_back(it.Value()); + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; +} + +const std::vector& MapperMaker::generated(const TopoDS_Shape& s) const +{ + _res.clear(); + try { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(maker.Generated(s)); it.More(); it.Next()) { + _res.push_back(it.Value()); + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; +} + +} // namespace Part diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index 9c642e65e2..8eac8afe17 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -16,4 +16,5 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShape.cpp ) diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp new file mode 100644 index 0000000000..11dd498309 --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +// Tests for the makeShape methods, extracted from the main set of tests for TopoShape +// due to length and complexity. + +#include "gtest/gtest.h" +#include "src/App/InitApplication.h" +#include "PartTestHelpers.h" +#include + +#include + +using namespace Data; +using namespace Part; +using namespace PartTestHelpers; + +class TopoShapeMakeShapeTests: 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; + } + + void TearDown() override + { + App::GetApplication().closeDocument(_docName.c_str()); + } + + Part::TopoShape* Shape() + { + return &_shape; + } + + Part::TopoShape::Mapper* Mapper() + { + return &_mapper; + } + +private: + std::string _docName; + Data::ElementIDRefs _sid; + QVector* _sids = nullptr; + Part::TopoShape _shape; + Part::TopoShape::Mapper _mapper; +}; + +TEST_F(TopoShapeMakeShapeTests, nullShapeThrows) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + std::vector sources {cube1, cube2}; + TopoDS_Vertex nullShape; + + // Act and assert + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullShape, *Mapper(), sources), + Part::NullShapeException); +} + +TEST_F(TopoShapeMakeShapeTests, shapeVertex) +{ + // Arrange + BRepBuilderAPI_MakeVertex vertexMaker = BRepBuilderAPI_MakeVertex(gp_Pnt(10, 10, 10)); + TopoShape topoShape(vertexMaker.Vertex(), 1L); + // Act + TopoShape& result = topoShape.makeElementShape(vertexMaker, topoShape); + auto elements = elementMap(result); + // Assert + EXPECT_EQ(elements.size(), 1); + EXPECT_EQ(elements.count(IndexedName("Vertex", 1)), 1); + EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;MAK;:H:4,V")); + EXPECT_EQ(getArea(result.getShape()), 0); +} + +TEST_F(TopoShapeMakeShapeTests, thruSections) +{ + // Arrange + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(); + TopoDS_Wire wire2 = wire1; + auto transform {gp_Trsf()}; + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.0, 0.5, 1.0)); + wire2.Move(TopLoc_Location(transform)); + TopoShape wire1ts {wire1, 1L}; + TopoShape wire2ts {wire2, 2L}; + BRepOffsetAPI_ThruSections thruMaker; + thruMaker.AddWire(wire1); + thruMaker.AddWire(wire2); + TopoShape topoShape {}; + // Act + TopoShape& result = topoShape.makeElementShape(thruMaker, {wire1ts, wire2ts}); + auto elements = elementMap(result); + // Assert + EXPECT_EQ(elements.size(), 24); + EXPECT_EQ(elements.count(IndexedName("Vertex", 1)), 1); + EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;TRU;:H1:4,V")); + EXPECT_EQ(getVolume(result.getShape()), 4); +} + +TEST_F(TopoShapeMakeShapeTests, sewing) +{ + // Arrange + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(); + auto face2 = face1; + auto transform {gp_Trsf()}; + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.5, 0.5, 0.0)); + face2.Move(TopLoc_Location(transform)); + BRepBuilderAPI_Sewing sewer; + sewer.Add(face1); + sewer.Add(face2); + sewer.Perform(); + std::vector sources {{face1, 1L}, {face2, 2L}}; + TopoShape topoShape {}; + // Act + TopoShape& result = topoShape.makeElementShape(sewer, sources); + auto elements = elementMap(result); + // Assert + EXPECT_EQ(&result, &topoShape); + EXPECT_EQ(elements.size(), 18); // Now a single cube + EXPECT_EQ(elements.count(IndexedName("Vertex", 1)), 1); + EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;SEW;:H1:4,V")); + EXPECT_EQ(getArea(result.getShape()), 12); +}