diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 92bd1030bb..defed8a154 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -893,12 +893,34 @@ private: App::Document *pcDoc = App::GetApplication().getActiveDocument(); if (!pcDoc) pcDoc = App::GetApplication().newDocument(); +#ifndef FC_USE_TNP_FIX TopoShapePy* pShape = static_cast(pcObj); Part::Feature *pcFeature = static_cast(pcDoc->addObject("Part::Feature", name)); // copy the data pcFeature->Shape.setValue(pShape->getTopoShapePtr()->getShape()); pcDoc->recompute(); return Py::asObject(pcFeature->getPyObject()); +#else + App::Document *pcSrcDoc = nullptr; + TopoShape shape; + if (PyObject_TypeCheck(pcObj, &TopoShapePy::Type)) + shape = *static_cast(pcObj)->getTopoShapePtr(); + else if (PyObject_TypeCheck(pcObj, &GeometryPy::Type)) + shape = static_cast(pcObj)->getGeometryPtr()->toShape(); + else if (PyObject_TypeCheck(pcObj, &App::DocumentObjectPy::Type)) { + auto obj = static_cast(pcObj)->getDocumentObjectPtr(); + pcSrcDoc = obj->getDocument(); + shape = Feature::getTopoShape(obj); + } + else + throw Py::TypeError("Expects argument of type DocumentObject, Shape, or Geometry"); + Part::Feature *pcFeature = static_cast(pcDoc->addObject("Part::Feature", name)); + // copy the data + pcFeature->Shape.setValue(shape); + pcFeature->purgeTouched(); + return Py::asObject(pcFeature->getPyObject()); +#endif + } Py::Object getFacets(const Py::Tuple& args) { diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index def84ac806..ad85d1b1ea 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -1015,6 +1015,22 @@ TopoShape Feature::getTopoShape(const App::DocumentObject* obj, hiddens, lastLink); + if (needSubElement && shape.shapeType(true) == TopAbs_COMPOUND) { + if (shape.countSubShapes(TopAbs_SOLID) == 1) + shape = shape.getSubTopoShape(TopAbs_SOLID, 1); + else if (shape.countSubShapes(TopAbs_COMPSOLID) == 1) + shape = shape.getSubTopoShape(TopAbs_COMPSOLID, 1); + else if (shape.countSubShapes(TopAbs_FACE) == 1) + shape = shape.getSubTopoShape(TopAbs_FACE, 1); + else if (shape.countSubShapes(TopAbs_SHELL) == 1) + shape = shape.getSubTopoShape(TopAbs_SHELL, 1); + else if (shape.countSubShapes(TopAbs_EDGE) == 1) + shape = shape.getSubTopoShape(TopAbs_EDGE, 1); + else if (shape.countSubShapes(TopAbs_WIRE) == 1) + shape = shape.getSubTopoShape(TopAbs_WIRE, 1); + else if (shape.countSubShapes(TopAbs_VERTEX) == 1) + shape = shape.getSubTopoShape(TopAbs_VERTEX, 1); + } Base::Matrix4D topMat; if (pmat || transform) { // Obtain top level transformation diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 037c39687d..24d9e4d6a2 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -5574,6 +5574,9 @@ TopoShape& TopoShape::makeElementBoolean(const char* maker, for (auto it = shapes.begin(); it != shapes.end(); ++it) { auto& s = *it; if (s.isNull()) { + if ( it == shapes.begin() ) { + return *this; // Compatible with pre-TNP allowing .fuse() behavior + } FC_THROWM(NullShapeException, "Null input shape"); } if (s.shapeType() == TopAbs_COMPOUND) { diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index e61490b9e9..5730f3e547 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -66,9 +66,9 @@ Feature::Feature() App::DocumentObjectExecReturn* Feature::recompute() { try { - auto baseShape = getBaseShape(); + auto baseShape = getBaseTopoShape(); if (Suppressed.getValue()) { - this->Shape.setValue(baseShape); + this->Shape.setValue(baseShape.getShape()); return StdReturn; } } @@ -106,9 +106,13 @@ TopoShape Feature::getSolid(const TopoShape& shape) if (shape.isNull()) { throw Part::NullShapeException("Null shape"); } - auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); - res.fixSolidOrientation(); - return res; + int count = shape.countSubShapes(TopAbs_SOLID); + if(count) { + auto res = shape.getSubTopoShape(TopAbs_SOLID,1); + res.fixSolidOrientation(); + return res; + } + return shape; } int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) diff --git a/src/Mod/PartDesign/App/FeatureChamfer.cpp b/src/Mod/PartDesign/App/FeatureChamfer.cpp index cd7ddd61c8..34c3679be8 100644 --- a/src/Mod/PartDesign/App/FeatureChamfer.cpp +++ b/src/Mod/PartDesign/App/FeatureChamfer.cpp @@ -108,7 +108,7 @@ App::DocumentObjectExecReturn *Chamfer::execute() // The only difference is that the Base property also stores the edges that are to be chamfered Part::TopoShape TopShape; try { - TopShape = getBaseShape(); + TopShape = getBaseTopoShape(); } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); diff --git a/src/Mod/PartDesign/App/FeatureDraft.cpp b/src/Mod/PartDesign/App/FeatureDraft.cpp index cc0a07d0c5..cc2e32f394 100644 --- a/src/Mod/PartDesign/App/FeatureDraft.cpp +++ b/src/Mod/PartDesign/App/FeatureDraft.cpp @@ -101,7 +101,7 @@ App::DocumentObjectExecReturn *Draft::execute() // Base shape Part::TopoShape TopShape; try { - TopShape = getBaseShape(); + TopShape = getBaseTopoShape(); } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); diff --git a/src/Mod/PartDesign/App/FeatureFillet.cpp b/src/Mod/PartDesign/App/FeatureFillet.cpp index b5bcbc2ba9..4b91088023 100644 --- a/src/Mod/PartDesign/App/FeatureFillet.cpp +++ b/src/Mod/PartDesign/App/FeatureFillet.cpp @@ -67,7 +67,7 @@ App::DocumentObjectExecReturn *Fillet::execute() { Part::TopoShape TopShape; try { - TopShape = getBaseShape(); + TopShape = getBaseTopoShape(); } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); diff --git a/src/Mod/PartDesign/App/FeatureGroove.cpp b/src/Mod/PartDesign/App/FeatureGroove.cpp index a71c95f7ff..8f15912f57 100644 --- a/src/Mod/PartDesign/App/FeatureGroove.cpp +++ b/src/Mod/PartDesign/App/FeatureGroove.cpp @@ -36,6 +36,7 @@ #include #include "FeatureGroove.h" +#include "Mod/Part/App/TopoShapeOpCode.h" using namespace PartDesign; @@ -78,6 +79,7 @@ short Groove::mustExecute() const return ProfileBased::mustExecute(); } +#ifndef FC_USE_TNP_FIX App::DocumentObjectExecReturn *Groove::execute() { // Validate parameters @@ -242,6 +244,133 @@ App::DocumentObjectExecReturn *Groove::execute() return new App::DocumentObjectExecReturn(e.what()); } } +#else +App::DocumentObjectExecReturn *Groove::execute() +{ + // Validate parameters + double angle = Angle.getValue(); + if (angle > 360.0) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too large")); + + angle = Base::toRadians(angle); + if (angle < Precision::Angular()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too small")); + + // Reverse angle if selected + if (Reversed.getValue() && !Midplane.getValue()) + angle *= (-1.0); + + TopoShape sketchshape; + try { + sketchshape = getVerifiedFace(); + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } + + // if the Base property has a valid shape, fuse the prism into it + TopoShape base; + try { + base = getBaseTopoShape(); + } + catch (const Base::Exception&) { + std::string text(QT_TRANSLATE_NOOP("Exception", "The requested feature cannot be created. The reason may be that:\n" + " - the active Body does not contain a base shape, so there is no\n" + " material to be removed;\n" + " - the selected sketch does not belong to the active Body.")); + return new App::DocumentObjectExecReturn(text); + } + + updateAxis(); + + // get revolve axis + Base::Vector3d b = Base.getValue(); + gp_Pnt pnt(b.x,b.y,b.z); + Base::Vector3d v = Axis.getValue(); + gp_Dir dir(v.x,v.y,v.z); + + try { + if (sketchshape.isNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); + + // Rotate the face by half the angle to get Groove symmetric to sketch plane + if (Midplane.getValue()) { + gp_Trsf mov; + mov.SetRotation(gp_Ax1(pnt, dir), Base::toRadians(Angle.getValue()) * (-1.0) / 2.0); + TopLoc_Location loc(mov); + sketchshape.move(loc); + } + + this->positionByPrevious(); + auto invObjLoc = getLocation().Inverted(); + pnt.Transform(invObjLoc.Transformation()); + dir.Transform(invObjLoc.Transformation()); + base.move(invObjLoc); + sketchshape.move(invObjLoc); + + // Check distance between sketchshape and axis - to avoid failures and crashes + TopExp_Explorer xp; + xp.Init(sketchshape.getShape(), TopAbs_FACE); + for (;xp.More(); xp.Next()) { + if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch")); + } + + // revolve the face to a solid + TopoShape result(0); + try { + result.makeElementRevolve(sketchshape, gp_Ax1(pnt, dir), angle); + }catch(Standard_Failure &) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!")); + } + this->AddSubShape.setValue(result); + + if(base.isNull()) { + Shape.setValue(getSolid(result)); + return App::DocumentObject::StdReturn; + } + + result.Tag = -getID(); + TopoShape boolOp(0); + + try { + const char *maker; + switch (getAddSubType()) { + case Additive: + maker = Part::OpCodes::Fuse; + break; +// case Intersecting: +// maker = Part::OpCodes::Common; +// break; + default: + maker = Part::OpCodes::Cut; + } +// this->fixShape(result); + boolOp.makeElementBoolean(maker, {base,result}); + }catch(Standard_Failure &) { + return new App::DocumentObjectExecReturn("Failed to cut base feature"); + } + boolOp = this->getSolid(boolOp); + if (boolOp.isNull()) + return new App::DocumentObjectExecReturn("Resulting shape is not a solid"); + + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + return App::DocumentObject::StdReturn; + } + catch (Standard_Failure& e) { + + if (std::string(e.GetMessageString()) == "TopoDS::Face") + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not create face from sketch.\n" + "Intersecting sketch entities in a sketch are not allowed.")); + else + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + catch (Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } +} + +#endif bool Groove::suggestReversed() { diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp index 97c52a42fb..05a932e377 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.cpp +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -192,13 +192,13 @@ App::DocumentObjectExecReturn* Helix::execute() } // if the Base property has a valid shape, fuse the AddShape into it - TopoDS_Shape base; + TopoShape base; try { - base = getBaseShape(); + base = getBaseTopoShape(); } catch (const Base::Exception&) { // fall back to support (for legacy features) - base = TopoDS_Shape(); + base = TopoShape(); } // update Axis from ReferenceAxis @@ -213,7 +213,7 @@ App::DocumentObjectExecReturn* Helix::execute() this->positionByPrevious(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); - base.Move(invObjLoc); + base.move(invObjLoc); std::vector wires; @@ -246,7 +246,7 @@ App::DocumentObjectExecReturn* Helix::execute() AddSubShape.setValue(result); - if (base.IsNull()) { + if (base.isNull()) { if (getAddSubType() == FeatureAddSub::Subtractive) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: There is nothing to subtract")); @@ -261,7 +261,7 @@ App::DocumentObjectExecReturn* Helix::execute() if (getAddSubType() == FeatureAddSub::Additive) { - BRepAlgoAPI_Fuse mkFuse(base, result); + BRepAlgoAPI_Fuse mkFuse(base.getShape(), result); if (!mkFuse.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Adding the helix failed")); // we have to get the solids (fuse sometimes creates compounds) @@ -284,14 +284,14 @@ App::DocumentObjectExecReturn* Helix::execute() TopoDS_Shape boolOp; if (Outside.getValue()) { // are we subtracting the inside or the outside of the profile. - BRepAlgoAPI_Common mkCom(result, base); + BRepAlgoAPI_Common mkCom(result, base.getShape()); if (!mkCom.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Intersecting the helix failed")); boolOp = this->getSolid(mkCom.Shape()); } else { - BRepAlgoAPI_Cut mkCut(base, result); + BRepAlgoAPI_Cut mkCut(base.getShape(), result); if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Subtracting the helix failed")); boolOp = this->getSolid(mkCut.Shape()); diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp index cc16eb0ac4..2dd745a03c 100644 --- a/src/Mod/PartDesign/App/FeatureHole.cpp +++ b/src/Mod/PartDesign/App/FeatureHole.cpp @@ -1652,7 +1652,7 @@ static gp_Pnt toPnt(gp_Vec dir) App::DocumentObjectExecReturn* Hole::execute() { - TopoDS_Shape profileshape; + TopoShape profileshape; try { profileshape = getVerifiedFace(); } @@ -1661,9 +1661,9 @@ App::DocumentObjectExecReturn* Hole::execute() } // Find the base shape - TopoDS_Shape base; + TopoShape base; try { - base = getBaseShape(); + base = getBaseTopoShape(); } catch (const Base::Exception&) { std::string text(QT_TRANSLATE_NOOP("Exception", "The requested feature cannot be created. The reason may be that:\n" @@ -1680,12 +1680,12 @@ App::DocumentObjectExecReturn* Hole::execute() this->positionByPrevious(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); - base.Move(invObjLoc); + base.move(invObjLoc); - if (profileshape.IsNull()) + if (profileshape.isNull()) return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Hole error: Creating a face from sketch failed")); - profileshape.Move(invObjLoc); + profileshape.move(invObjLoc); /* Build the prototype hole */ @@ -1883,11 +1883,11 @@ App::DocumentObjectExecReturn* Hole::execute() protoHole = mkFuse.Shape(); } - TopoDS_Compound holes = findHoles(profileshape, protoHole); + TopoDS_Compound holes = findHoles(profileshape.getShape(), protoHole); this->AddSubShape.setValue(holes); // For some reason it is faster to do the cut through a BooleanOperation. - BRepAlgoAPI_Cut mkBool(base, holes); + BRepAlgoAPI_Cut mkBool(base.getShape(), holes); if (!mkBool.IsDone()) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Boolean operation failed")); } @@ -1896,13 +1896,13 @@ App::DocumentObjectExecReturn* Hole::execute() // We have to get the solids (fuse sometimes creates compounds) base = getSolid(result); - if (base.IsNull()) + if (base.isNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); base = refineShapeIfActive(base); - int solidCount = countSolids(base); + int solidCount = countSolids(base.getShape()); if (solidCount > 1) { return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported.")); diff --git a/src/Mod/PartDesign/App/FeatureLoft.cpp b/src/Mod/PartDesign/App/FeatureLoft.cpp index 8e9811f354..b067af4f4f 100644 --- a/src/Mod/PartDesign/App/FeatureLoft.cpp +++ b/src/Mod/PartDesign/App/FeatureLoft.cpp @@ -43,6 +43,7 @@ #include #include "FeatureLoft.h" +#include "Mod/Part/App/TopoShapeOpCode.h" using namespace PartDesign; @@ -69,6 +70,7 @@ short Loft::mustExecute() const return ProfileBased::mustExecute(); } +#ifndef FC_USE_TNP_FIX App::DocumentObjectExecReturn *Loft::execute() { auto getSectionShape = @@ -330,7 +332,215 @@ App::DocumentObjectExecReturn *Loft::execute() return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Loft: A fatal error occurred when making the loft")); } } +#else +std::vector +Loft::getSectionShape(const char *name, + App::DocumentObject *obj, + const std::vector &subs, + size_t expected_size) +{ + std::vector shapes; + if (subs.empty() || std::find(subs.begin(), subs.end(), std::string()) != subs.end()) { + shapes.push_back(Part::Feature::getTopoShape(obj)); + if (shapes.back().isNull()) + FC_THROWM(Part::NullShapeException, "Failed to get shape of " + << name << " " << App::SubObjectT(obj, "").getSubObjectFullName(obj->getDocument()->getName())); + } else { + for (const auto &sub : subs) { + shapes.push_back(Part::Feature::getTopoShape(obj, sub.c_str(), /*needSubElement*/true)); + if (shapes.back().isNull()) + FC_THROWM(Part::NullShapeException, "Failed to get shape of " << name << " " + << App::SubObjectT(obj, sub.c_str()).getSubObjectFullName(obj->getDocument()->getName())); + } + } + auto compound = TopoShape().makeElementCompound(shapes, "", TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + auto wires = compound.getSubTopoShapes(TopAbs_WIRE); + auto edges = compound.getSubTopoShapes(TopAbs_EDGE, TopAbs_WIRE); // get free edges and make wires from it + if (edges.size()) { + auto extra = TopoShape().makeElementWires(edges).getSubTopoShapes(TopAbs_WIRE); + wires.insert(wires.end(), extra.begin(), extra.end()); + } + const char *msg = "Sections need to have the same amount of wires or vertices as the base section"; + if (!wires.empty()) { + if (expected_size && expected_size != wires.size()) + FC_THROWM(Base::CADKernelError, msg); + return wires; + } + auto vertices = compound.getSubTopoShapes(TopAbs_VERTEX); + if (vertices.empty()) + FC_THROWM(Base::CADKernelError, "Invalid " << name << " shape, expecting either wires or vertices"); + if (expected_size && expected_size != vertices.size()) + FC_THROWM(Base::CADKernelError, msg); + return vertices; +} +App::DocumentObjectExecReturn *Loft::execute(void) +{ + std::vector wires; + try { + wires = getSectionShape("Profile", Profile.getValue(), Profile.getSubValues()); + } catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } + + // if the Base property has a valid shape, fuse the pipe into it + TopoShape base; + try { + base = getBaseTopoShape(); + } catch (const Base::Exception&) { + } + + try { + //setup the location + this->positionByPrevious(); + auto invObjLoc = this->getLocation().Inverted(); + if(!base.isNull()) + base.move(invObjLoc); + + //build up multisections + auto multisections = Sections.getSubListValues(); + if(multisections.empty()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Loft: At least one section is needed")); + + std::vector> wiresections; + wiresections.reserve(wires.size()); + for(auto& wire : wires) + wiresections.emplace_back(1, wire); + + for (const auto &subSet : multisections) { + int i=0; + for (const auto &s : getSectionShape("Section", subSet.first, subSet.second, wiresections.size())) + wiresections[i++].push_back(s); + } + + TopoShape result(0); + std::vector shapes; + +// if (SplitProfile.getValue()) { +// for (auto &wires : wiresections) { +// for(auto& wire : wires) +// wire.move(invObjLoc); +// shapes.push_back(TopoShape(0).makeElementLoft( +// wires, Part::IsSolid::solid, Ruled.getValue(), Closed.getValue())); +// } +// } else { + //build all shells + std::vector shells; + for (auto &wires : wiresections) { + for(auto& wire : wires) + wire.move(invObjLoc); + shells.push_back(TopoShape(0).makeElementLoft( + wires, Part::IsSolid::notSolid, Ruled.getValue()? Part::IsRuled::ruled : Part::IsRuled::notRuled, Closed.getValue() ? Part::IsClosed::closed : Part::IsClosed::notClosed)); +// } + + //build the top and bottom face, sew the shell and build the final solid + TopoShape front; + if (wiresections[0].front().shapeType() != TopAbs_VERTEX) { + front = getVerifiedFace(); + if (front.isNull()) + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Loft: Creating a face from sketch failed")); + front.move(invObjLoc); + } + + TopoShape back; + if (wiresections[0].back().shapeType() != TopAbs_VERTEX) { + std::vector backwires; + for(auto& wires : wiresections) + backwires.push_back(wires.back()); + back = TopoShape(0).makeElementFace(backwires); + } + + if (!front.isNull() || !back.isNull()) { + BRepBuilderAPI_Sewing sewer; + sewer.SetTolerance(Precision::Confusion()); + if (!front.isNull()) + sewer.Add(front.getShape()); + if (!back.isNull()) + sewer.Add(back.getShape()); + for(auto& s : shells) + sewer.Add(s.getShape()); + + sewer.Perform(); + + if (!front.isNull()) + shells.push_back(front); + if (!back.isNull()) + shells.push_back(back); +// result = result.makeElementShape(sewer,shells); + result = result.makeShapeWithElementMap(sewer.SewedShape(), Part::MapperSewing(sewer), shells, Part::OpCodes::Sewing); + } + + if(!result.countSubShapes(TopAbs_SHELL)) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Loft: Failed to create shell")); + shapes = result.getSubTopoShapes(TopAbs_SHELL); + } + + for (auto &s : shapes) { + //build the solid + s = s.makeElementSolid(); + BRepClass3d_SolidClassifier SC(s.getShape()); + SC.PerformInfinitePoint(Precision::Confusion()); + if ( SC.State() == TopAbs_IN) + s.setShape(s.getShape().Reversed(),false); + } + + AddSubShape.setValue(result.makeElementCompound(shapes, nullptr, Part::TopoShape::SingleShapeCompoundCreationPolicy::returnShape)); + + if (shapes.size() > 1) + result.makeElementFuse(shapes); + else + result = shapes.front(); + + if(base.isNull()) { + Shape.setValue(getSolid(result)); + return App::DocumentObject::StdReturn; + } + + result.Tag = -getID(); + TopoShape boolOp(0,getDocument()->getStringHasher()); + + const char *maker; + switch(getAddSubType()) { + case Additive: + maker = Part::OpCodes::Fuse; + break; + case Subtractive: + maker = Part::OpCodes::Cut; + break; +// case Intersecting: +// maker = Part::OpCodes::Common; +// break; + default: + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Unknown operation type")); + } + try { + boolOp.makeElementBoolean(maker, {base,result}); + } + catch(Standard_Failure &e) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Failed to perform boolean operation")); + } + boolOp = this->getSolid(boolOp); + // lets check if the result is a solid + if (boolOp.isNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); + + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + return App::DocumentObject::StdReturn; + } + catch (Standard_Failure& e) { + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + catch (const Base::Exception& e) { + return new App::DocumentObjectExecReturn(e.what()); + } + catch (...) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Loft: A fatal error occurred when making the loft")); + } +} + +#endif PROPERTY_SOURCE(PartDesign::AdditiveLoft, PartDesign::Loft) AdditiveLoft::AdditiveLoft() { diff --git a/src/Mod/PartDesign/App/FeatureLoft.h b/src/Mod/PartDesign/App/FeatureLoft.h index dc6fd321f8..497a4ee2db 100644 --- a/src/Mod/PartDesign/App/FeatureLoft.h +++ b/src/Mod/PartDesign/App/FeatureLoft.h @@ -50,6 +50,11 @@ public: } //@} + static std::vector getSectionShape(const char *name, + App::DocumentObject *obj, + const std::vector &subname, + size_t expected_size = 0); + protected: // handle changed property void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override; diff --git a/src/Mod/PartDesign/App/FeaturePipe.cpp b/src/Mod/PartDesign/App/FeaturePipe.cpp index 032fca9dfe..42ff8c26d6 100644 --- a/src/Mod/PartDesign/App/FeaturePipe.cpp +++ b/src/Mod/PartDesign/App/FeaturePipe.cpp @@ -48,7 +48,11 @@ #include #include "FeaturePipe.h" +#include "Mod/Part/App/TopoShapeOpCode.h" +#include "Mod/Part/App/TopoShapeMapper.h" +#include "FeatureLoft.h" +FC_LOG_LEVEL_INIT("PartDesign",true,true); using namespace PartDesign; @@ -98,6 +102,7 @@ short Pipe::mustExecute() const return ProfileBased::mustExecute(); } +#ifndef FC_USE_TNP_FIX App::DocumentObjectExecReturn *Pipe::execute() { auto getSectionShape = [](App::DocumentObject* feature, @@ -433,8 +438,345 @@ App::DocumentObjectExecReturn *Pipe::execute() return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "A fatal error occurred when making the pipe")); } } +#else +App::DocumentObjectExecReturn *Pipe::execute() +{ + auto getSectionShape = [](App::DocumentObject* feature, + const std::vector& subs) -> TopoDS_Shape { + if (!feature || !feature->isDerivedFrom(Part::Feature::getClassTypeId())) + throw Base::TypeError("Pipe: Invalid profile/section"); -void Pipe::setupAlgorithm(BRepOffsetAPI_MakePipeShell& mkPipeShell, TopoDS_Shape& auxshape) { + auto subName = subs.empty() ? "" : subs.front(); + + // only take the entire shape when we have a sketch selected, but + // not a point of the sketch + if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) + && subName.compare(0, 6, "Vertex") != 0) + return static_cast(feature)->Shape.getValue(); + else { + if (subName.empty()) + throw Base::ValueError("Pipe: No valid subelement linked in Part::Feature"); + return static_cast(feature)->Shape.getShape().getSubShape( + subName.c_str()); + } + }; + + auto addWiresToWireSections = + [](TopoDS_Shape& section, std::vector>& wiresections) -> size_t { + TopExp_Explorer ex; + size_t i = 0; + bool initialWireSectionsEmpty = wiresections.empty(); + for (ex.Init(section, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { + // if profile was just a point then this is where we can first set our list + if (i >= wiresections.size()) { + if (initialWireSectionsEmpty) + wiresections.emplace_back(1, ex.Current()); + else + throw Base::ValueError( + "Pipe: Sections need to have the same amount of inner wires (except " + "profile and last section, which can be points)"); + } + else + wiresections[i].push_back(TopoDS::Wire(ex.Current())); + } + return i; + }; + + // TODO: currently we can only allow planar faces, so add that check. + // The reason for this is that with other faces in front, we could not use the + // current simulate approach and build the start and end face from the wires. + // As the shell begins always at the spine and not the profile, the sketchshape + // cannot be used directly as front face. We would need a method to translate + // the front shape to match the shell starting position somehow... + std::vector wires; + TopoDS_Shape profilePoint; + + // if the Base property has a valid shape, fuse the pipe into it + TopoShape base; + try { + base = getBaseTopoShape(); + } catch (const Base::Exception&) { + base = TopoShape(); + } + + try { + // setup the location + this->positionByPrevious(); + TopLoc_Location invObjLoc = this->getLocation().Inverted(); + if (!base.isNull()) + base.move(invObjLoc); + + // setup the profile section + TopoDS_Shape profileShape = getSectionShape(Profile.getValue(), + Profile.getSubValues()); + if (profileShape.IsNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Pipe: Could not obtain profile shape")); + + // build the paths + App::DocumentObject* spine = Spine.getValue(); + if (!(spine && spine->isDerivedFrom())) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "No spine linked")); + + std::vector subedge = Spine.getSubValues(); + TopoDS_Shape path; + const Part::TopoShape& shape = static_cast(spine)->Shape.getValue(); + buildPipePath(shape, subedge, path); + path.Move(invObjLoc); + + // auxiliary + TopoDS_Shape auxpath; + if (Mode.getValue() == 3) { + App::DocumentObject* auxspine = AuxillerySpine.getValue(); + if (!(auxspine && auxspine->isDerivedFrom())) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "No auxiliary spine linked.")); + std::vector auxsubedge = AuxillerySpine.getSubValues(); + + const Part::TopoShape& auxshape = + static_cast(auxspine)->Shape.getValue(); + buildPipePath(auxshape, auxsubedge, auxpath); + auxpath.Move(invObjLoc); + } + + // build up multisections + auto multisections = Sections.getSubListValues(); + std::vector> wiresections; + + size_t numWires = addWiresToWireSections(profileShape, wiresections); + if (numWires == 0) { + // profileShape had no wires so only other valid option is single point section + TopExp_Explorer ex; + size_t i = 0; + for (ex.Init(profileShape, TopAbs_VERTEX); ex.More(); ex.Next(), ++i) + profilePoint = ex.Current(); + if (i > 1) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Pipe: Only one isolated point is needed if using a sketch with isolated " + "points for section")); + } + + if (!profilePoint.IsNull() && (Transformation.getValue() != 1 || multisections.empty())) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Pipe: At least one section is needed when using a single point for profile")); + + // maybe we need a scaling law + Handle(Law_Function) scalinglaw; + + bool isLastSectionVertex = false; + + // see if we shall use multiple sections + if (Transformation.getValue() == 1) { + // TODO: we need to order the sections to prevent occ from crashing, + // as makepipeshell connects the sections in the order of adding + for (auto& subSet : multisections) { + if (!subSet.first->isDerivedFrom(Part::Feature::getClassTypeId())) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Pipe: All sections need to be part features")); + + // if the section is an object's face then take just the face + TopoDS_Shape shape = getSectionShape(subSet.first, subSet.second); + if (shape.IsNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Pipe: Could not obtain section shape")); + + size_t nWiresAdded = addWiresToWireSections(shape, wiresections); + if (nWiresAdded == 0) { + TopExp_Explorer ex; + size_t i = 0; + for (ex.Init(shape, TopAbs_VERTEX); ex.More(); ex.Next(), ++i) { + if (isLastSectionVertex) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Pipe: Only the profile and last section can be vertices")); + isLastSectionVertex = true; + for (auto& wires : wiresections) + wires.push_back(ex.Current()); + } + } + + if (!isLastSectionVertex && nWiresAdded < wiresections.size()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Multisections need to have the same amount of inner wires as the base " + "section")); + } + } + /*//build the law functions instead + else if (Transformation.getValue() == 2) { + if (ScalingData.getValues().size()<1) + return new App::DocumentObjectExecReturn("No valid data given for linear scaling mode"); + + Handle(Law_Linear) lin = new Law_Linear(); + lin->Set(0, 1, 1, ScalingData[0].x); + + scalinglaw = lin; + } + else if (Transformation.getValue() == 3) { + if (ScalingData.getValues().size()<1) + return new App::DocumentObjectExecReturn("No valid data given for S-shape scaling mode"); + + Handle(Law_S) s = new Law_S(); + s->Set(0, 1, ScalingData[0].y, 1, ScalingData[0].x, ScalingData[0].z); + + scalinglaw = s; + }*/ + + // Verify that path is not a null shape + if (path.IsNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( + "Exception", "Path must not be a null shape")); + + // build all shells + std::vector shells; + + TopoDS_Shape copyProfilePoint(profilePoint); + if (!profilePoint.IsNull()) + copyProfilePoint.Move(invObjLoc); + + std::vector frontwires, backwires; + for (auto& wires : wiresections) { + BRepOffsetAPI_MakePipeShell mkPS(TopoDS::Wire(path)); + setupAlgorithm(mkPS, auxpath); + + if (!scalinglaw) { + if (!profilePoint.IsNull()) + mkPS.Add(copyProfilePoint); + + for (auto& wire : wires) { + wire.Move(invObjLoc); + mkPS.Add(wire); + } + } + else { + if (!profilePoint.IsNull()) + mkPS.SetLaw(copyProfilePoint, scalinglaw); + + for (auto& wire : wires) { + wire.Move(invObjLoc); + mkPS.SetLaw(wire, scalinglaw); + } + } + + if (!mkPS.IsReady()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Pipe could not be built")); + + shells.push_back(mkPS.Shape()); + + if (!mkPS.Shape().Closed()) { + // shell is not closed - use simulate to get the end wires + TopTools_ListOfShape sim; + mkPS.Simulate(2, sim); + + // Note that while we call them front and back, these sections + // appear to correspond to the front or back of the path. When one + // or both ends of the pipe are points, one or both of these wires + // (and eventually faces) will be null. + frontwires.push_back(TopoDS::Wire(sim.First())); + backwires.push_back(TopoDS::Wire(sim.Last())); + } + } + + BRepBuilderAPI_MakeSolid mkSolid; + + if (!frontwires.empty() || !backwires.empty()) { + BRepBuilderAPI_Sewing sewer; + sewer.SetTolerance(Precision::Confusion()); + + // build the end faces, sew the shell and build the final solid + if (!frontwires.empty()) { + TopoDS_Shape front = Part::FaceMakerCheese::makeFace(frontwires); + sewer.Add(front); + } + if (!backwires.empty()) { + TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); + sewer.Add(back); + } + for (TopoDS_Shape& s : shells) + sewer.Add(s); + + sewer.Perform(); + mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); + } else { + // shells are already closed - add them directly + for (TopoDS_Shape& s : shells) { + mkSolid.Add(TopoDS::Shell(s)); + } + } + + if (!mkSolid.IsDone()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result is not a solid")); + + TopoDS_Shape result = mkSolid.Shape(); + BRepClass3d_SolidClassifier SC(result); + SC.PerformInfinitePoint(Precision::Confusion()); + if (SC.State() == TopAbs_IN) { + result.Reverse(); + } + + //result.Move(invObjLoc); + AddSubShape.setValue(result); // TODO: Do we need to toposhape here? + + if (base.isNull()) { + if (getAddSubType() == FeatureAddSub::Subtractive) + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Pipe: There is nothing to subtract from")); + + result = refineShapeIfActive(result); + Shape.setValue(getSolid(result)); + return App::DocumentObject::StdReturn; + } + + if (getAddSubType() == FeatureAddSub::Additive) { + + BRepAlgoAPI_Fuse mkFuse(base.getShape(), result); + if (!mkFuse.IsDone()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Adding the pipe failed")); + // we have to get the solids (fuse sometimes creates compounds) + TopoShape boolOp = this->getSolid(mkFuse.Shape()); + // lets check if the result is a solid + if (boolOp.isNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); + + int solidCount = countSolids(boolOp.getShape()); + if (solidCount > 1) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Result has multiple solids: that is not currently supported.")); + } + + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + } + else if (getAddSubType() == FeatureAddSub::Subtractive) { + + BRepAlgoAPI_Cut mkCut(base.getShape(), result); + if (!mkCut.IsDone()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Subtracting the pipe failed")); + // we have to get the solids (fuse sometimes creates compounds) + TopoShape boolOp = this->getSolid(mkCut.Shape()); + // lets check if the result is a solid + if (boolOp.isNull()) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting shape is not a solid")); + + int solidCount = countSolids(boolOp.getShape()); + if (solidCount > 1) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", + "Result has multiple solids: that is not currently supported.")); + } + + boolOp = refineShapeIfActive(boolOp); + Shape.setValue(getSolid(boolOp)); + } + + return App::DocumentObject::StdReturn; + } + catch (Standard_Failure& e) { + + return new App::DocumentObjectExecReturn(e.GetMessageString()); + } + catch (...) { + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "A fatal error occurred when making the pipe")); + } +} + +#endif +void Pipe::setupAlgorithm(BRepOffsetAPI_MakePipeShell& mkPipeShell, const TopoDS_Shape& auxshape) { mkPipeShell.SetTolerance(Precision::Confusion()); @@ -581,7 +923,6 @@ void Pipe::buildPipePath(const Part::TopoShape& shape, const std::vector &multisections = {}, + bool moveProfile = false, + bool rotateProfile = false); protected: /// get the given edges and all their tangent ones void getContinuousEdges(Part::TopoShape TopShape, std::vector< std::string >& SubNames); void buildPipePath(const Part::TopoShape& input, const std::vector& edges, TopoDS_Shape& result); - void setupAlgorithm(BRepOffsetAPI_MakePipeShell& mkPipeShell, TopoDS_Shape& auxshape); + void setupAlgorithm(BRepOffsetAPI_MakePipeShell& mkPipeShell, const TopoDS_Shape& auxshape); /// handle changed property void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override; diff --git a/src/Mod/PartDesign/App/FeaturePocket.cpp b/src/Mod/PartDesign/App/FeaturePocket.cpp index b85b7ed385..9cd2bf7c69 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.cpp +++ b/src/Mod/PartDesign/App/FeaturePocket.cpp @@ -87,9 +87,9 @@ App::DocumentObjectExecReturn *Pocket::execute() } // if the Base property has a valid shape, fuse the prism into it - TopoDS_Shape base; + TopoShape base; try { - base = getBaseShape(); + base = getBaseTopoShape(); } catch (const Base::Exception&) { std::string text(QT_TRANSLATE_NOOP("Exception", ("The requested feature cannot be created. The reason may be that:\n" @@ -109,7 +109,7 @@ App::DocumentObjectExecReturn *Pocket::execute() this->positionByPrevious(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); - base.Move(invObjLoc); + base.move(invObjLoc); Base::Vector3d pocketDirection = computeDirection(SketchVector); @@ -145,7 +145,7 @@ App::DocumentObjectExecReturn *Pocket::execute() std::string method(Type.getValueAsString()); if (method == "UpToFirst" || method == "UpToFace") { - if (base.IsNull()) + if (base.isNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Pocket: Extruding up to a face is only possible if the sketch is located on a face")); // Note: This will return an unlimited planar face if support is a datum plane @@ -161,7 +161,7 @@ App::DocumentObjectExecReturn *Pocket::execute() getFaceFromLinkSub(upToFace, UpToFace); upToFace.Move(invObjLoc); } - getUpToFace(upToFace, base, profileshape, method, dir); + getUpToFace(upToFace, base.getShape(), profileshape, method, dir); addOffsetToFace(upToFace, dir, Offset.getValue()); // BRepFeat_MakePrism(..., 2, 1) in combination with PerForm(upToFace) is buggy when the @@ -176,10 +176,10 @@ App::DocumentObjectExecReturn *Pocket::execute() supportface = TopoDS_Face(); TopoDS_Shape prism; PrismMode mode = PrismMode::CutFromBase; - generatePrism(prism, method, base, profileshape, supportface, upToFace, dir, mode, Standard_True); + generatePrism(prism, method, base.getShape(), profileshape, supportface, upToFace, dir, mode, Standard_True); // And the really expensive way to get the SubShape... - BRepAlgoAPI_Cut mkCut(base, prism); + BRepAlgoAPI_Cut mkCut(base.getShape(), prism); if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Pocket: Up to face: Could not get SubShape!")); // FIXME: In some cases this affects the Shape property: It is set to the same shape as the SubShape!!!! @@ -212,7 +212,7 @@ App::DocumentObjectExecReturn *Pocket::execute() this->AddSubShape.setValue(prism); // Cut the SubShape out of the base feature - BRepAlgoAPI_Cut mkCut(base, prism); + BRepAlgoAPI_Cut mkCut(base.getShape(), prism); if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Pocket: Cut out of base feature failed")); TopoDS_Shape result = mkCut.Shape(); diff --git a/src/Mod/PartDesign/App/FeatureRevolution.cpp b/src/Mod/PartDesign/App/FeatureRevolution.cpp index 769e2545ef..b03e48d175 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.cpp +++ b/src/Mod/PartDesign/App/FeatureRevolution.cpp @@ -100,12 +100,12 @@ App::DocumentObjectExecReturn *Revolution::execute() } // if the Base property has a valid shape, fuse the AddShape into it - TopoDS_Shape base; + TopoShape base; try { - base = getBaseShape(); + base = getBaseTopoShape(); } catch (const Base::Exception&) { // fall back to support (for legacy features) - base = TopoDS_Shape(); + base = TopoShape(); } // update Axis from ReferenceAxis @@ -131,7 +131,7 @@ App::DocumentObjectExecReturn *Revolution::execute() TopLoc_Location invObjLoc = this->getLocation().Inverted(); pnt.Transform(invObjLoc.Transformation()); dir.Transform(invObjLoc.Transformation()); - base.Move(invObjLoc); + base.move(invObjLoc); sketchshape.Move(invObjLoc); // Check distance between sketchshape and axis - to avoid failures and crashes @@ -169,7 +169,7 @@ App::DocumentObjectExecReturn *Revolution::execute() if (!Ex.More()) supportface = TopoDS_Face(); RevolMode mode = RevolMode::None; - generateRevolution(result, base, sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True); + generateRevolution(result, base.getShape(), sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True); } else { bool midplane = Midplane.getValue(); @@ -182,9 +182,9 @@ App::DocumentObjectExecReturn *Revolution::execute() // set the additive shape property for later usage in e.g. pattern this->AddSubShape.setValue(result); - if (!base.IsNull()) { + if (!base.isNull()) { // Let's call algorithm computing a fuse operation: - BRepAlgoAPI_Fuse mkFuse(base, result); + BRepAlgoAPI_Fuse mkFuse(base.getShape(), result); // Let's check if the fusion has been successful if (!mkFuse.IsDone()) throw Part::BooleanException(QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed")); diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 1673d65e45..dd1fc773ef 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -908,11 +908,11 @@ void ProfileBased::addOffsetToFace(TopoShape& upToFace, const gp_Dir& dir, doubl double ProfileBased::getThroughAllLength() const { TopoDS_Shape profileshape; - TopoDS_Shape base; + TopoShape base; profileshape = getVerifiedFace(); - base = getBaseShape(); + base = getBaseTopoShape(); Bnd_Box box; - BRepBndLib::Add(base, box); + BRepBndLib::Add(base.getShape(), box); BRepBndLib::Add(profileshape, box); box.SetGap(0.0); // The diagonal of the bounding box, plus 1% extra to eliminate risk of diff --git a/src/Mod/PartDesign/App/FeatureThickness.cpp b/src/Mod/PartDesign/App/FeatureThickness.cpp index 7d647a21e7..b7228dedb9 100644 --- a/src/Mod/PartDesign/App/FeatureThickness.cpp +++ b/src/Mod/PartDesign/App/FeatureThickness.cpp @@ -66,7 +66,7 @@ App::DocumentObjectExecReturn *Thickness::execute() // Base shape Part::TopoShape TopShape; try { - TopShape = getBaseShape(); + TopShape = getBaseTopoShape(); } catch (Base::Exception &e) { return new App::DocumentObjectExecReturn(e.what()); diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index e35271370b..a027aca68b 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -549,7 +549,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): self.Doc.recompute() # Assert self.assertEqual(len(body.Shape.childShapes()), 1) - self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30) def testPartDesignElementMapPipe(self): # Arrange