From cfdf334b7f45b6ac0d6432890b1a9c5792ddaaa5 Mon Sep 17 00:00:00 2001 From: Uwe Date: Fri, 7 Jan 2022 05:15:28 +0100 Subject: [PATCH] [PD] add option to create tapered Pad / Pocket This PR adds the same functionality as provided by Part Extrude. The used code parts are sorted out to a new helper function that is used by Part and PartDesign. --- src/Mod/Part/App/CMakeLists.txt | 2 + src/Mod/Part/App/ExtrusionHelper.cpp | 508 ++++++++++++++++++ src/Mod/Part/App/ExtrusionHelper.h | 73 +++ src/Mod/Part/App/FeatureExtrusion.cpp | 440 +-------------- src/Mod/Part/App/FeatureExtrusion.h | 23 - src/Mod/PartDesign/App/FeatureExtrude.cpp | 6 +- src/Mod/PartDesign/App/FeatureExtrude.h | 6 + src/Mod/PartDesign/App/FeaturePad.cpp | 12 +- src/Mod/PartDesign/App/FeaturePocket.cpp | 10 +- src/Mod/PartDesign/App/FeatureSketchBased.cpp | 116 +++- src/Mod/PartDesign/App/FeatureSketchBased.h | 40 +- src/Mod/PartDesign/App/PreCompiled.h | 2 + .../PartDesign/Gui/TaskExtrudeParameters.cpp | 40 ++ .../PartDesign/Gui/TaskExtrudeParameters.h | 4 +- src/Mod/PartDesign/Gui/TaskPadParameters.cpp | 4 + .../PartDesign/Gui/TaskPadPocketParameters.ui | 59 +- .../PartDesign/Gui/TaskPocketParameters.cpp | 4 + 17 files changed, 842 insertions(+), 507 deletions(-) create mode 100644 src/Mod/Part/App/ExtrusionHelper.cpp create mode 100644 src/Mod/Part/App/ExtrusionHelper.h diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index dfec4362bf..2d4b373e4d 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -425,6 +425,8 @@ SET(Part_SRCS BSplineCurveBiArcs.cpp CrossSection.cpp CrossSection.h + ExtrusionHelper.cpp + ExtrusionHelper.h GeometryExtension.cpp GeometryExtension.h GeometryDefaultExtension.cpp diff --git a/src/Mod/Part/App/ExtrusionHelper.cpp b/src/Mod/Part/App/ExtrusionHelper.cpp new file mode 100644 index 0000000000..7dfe5deaec --- /dev/null +++ b/src/Mod/Part/App/ExtrusionHelper.cpp @@ -0,0 +1,508 @@ +/*************************************************************************** + * Copyright (c) 2022 Uwe Stöhr * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include "ExtrusionHelper.h" +#include +#include +#include +#include "FeatureExtrusion.h" + +using namespace Part; + +ExtrusionHelper::ExtrusionHelper() +{ +} + +void ExtrusionHelper::makeDraft(const TopoDS_Shape& shape, + const gp_Dir& direction, + const double LengthFwd, + const double LengthRev, + const double AngleFwd, + const double AngleRev, + bool isSolid, + std::list& drafts, + bool isPartDesign) +{ + std::vector> wiresections; + + auto addWiresToWireSections = + [&shape](std::vector>& wiresections) -> size_t { + TopExp_Explorer ex; + size_t i = 0; + for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { + wiresections.push_back(std::vector()); + wiresections[i].push_back(TopoDS::Wire(ex.Current())); + } + return i; + }; + + double distanceFwd = tan(AngleFwd) * LengthFwd; + double distanceRev = tan(AngleRev) * LengthRev; + gp_Vec vecFwd = gp_Vec(direction) * LengthFwd; + gp_Vec vecRev = gp_Vec(direction.Reversed()) * LengthRev; + + bool bFwd = fabs(LengthFwd) > Precision::Confusion(); + bool bRev = fabs(LengthRev) > Precision::Confusion(); + // only if there is a 2nd direction and the negated angle is equal to the first one + // we can omit the source shape as loft section + bool bMid = !bFwd || !bRev || -1.0 * AngleFwd != AngleRev; + + if (shape.IsNull()) + Standard_Failure::Raise("Not a valid shape"); + + // store all wires of the shape into an array + size_t numWires = addWiresToWireSections(wiresections); + if (numWires == 0) + Standard_Failure::Raise("Extrusion: Input must not only consist if a vertex"); + + // to store the sections for the loft + std::list list_of_sections; + + // we need for all found wires an offset copy of them + // we store them in an array + TopoDS_Wire offsetWire; + std::vector> extrusionSections(wiresections.size(), std::vector()); + size_t rows = 0; + int numEdges = 0; + + // We need to find out what are outer wires and what are inner ones + // methods like checking the center of mass etc. don't help us here. + // As solution we build a prism with every wire, then subtract every prism from each other. + // If the moment of inertia changes by a subtraction, we have an inner wire prism. + // + // first build the prisms + std::vector resultPrisms; + TopoDS_Shape singlePrism; + for (auto& wireVector : wiresections) { + for (auto& singleWire : wireVector) { + BRepBuilderAPI_MakeFace mkFace(TopoDS::Wire(singleWire)); + auto tempFace = mkFace.Shape(); + BRepPrimAPI_MakePrism mkPrism(tempFace, vecFwd); + if (!mkPrism.IsDone()) + Standard_Failure::Raise("Extrusion: Generating prism failed"); + singlePrism = mkPrism.Shape(); + resultPrisms.push_back(singlePrism); + } + } + // create an array with false to store later which wires are inner ones + std::vector isInnerWire(resultPrisms.size(), false); + std::vector checklist(resultPrisms.size(), true); + // finally check reecursively for inner wires + checkInnerWires(isInnerWire, direction, checklist, false, resultPrisms); + + // count the number of inner wires + int numInnerWires = 0; + for (auto isInner : isInnerWire) { + if (isInner) + ++numInnerWires; + } + + // at first create offset wires for the reversed part of extrusion + // it is important that these wires are the first loft section + if (bRev) { + // create an offset for all source wires + rows = 0; + for (auto& wireVector : wiresections) { + for (auto& singleWire : wireVector) { + // count number of edges + numEdges = 0; + TopExp_Explorer xp(singleWire, TopAbs_EDGE); + while (xp.More()) { + numEdges++; + xp.Next(); + } + // create an offset copy of the wire + if (!isInnerWire[rows]) { + // this is an outer wire + createTaperedPrismOffset(TopoDS::Wire(singleWire), vecRev, distanceRev, numEdges, true, offsetWire); + } + else { + // there is an OCC bug with single-edge wires (circles), see inside createTaperedPrismOffset + if (numEdges > 1 || !isPartDesign) + // inner wires must get the negated offset + createTaperedPrismOffset(TopoDS::Wire(singleWire), vecRev, -distanceRev, numEdges, true, offsetWire); + else + // these wires must not get the negated offset + createTaperedPrismOffset(TopoDS::Wire(singleWire), vecRev, distanceRev, numEdges, true, offsetWire); + } + if (offsetWire.IsNull()) + return; + extrusionSections[rows].push_back(offsetWire); + } + ++rows; + } + } + + // add the source wire as middle section + // it is important to add them after the reversed part + if (bMid) { + // transfer all source wires as they are to the array from which we build the shells + rows = 0; + for (auto& wireVector : wiresections) { + for (auto& singleWire : wireVector) { + extrusionSections[rows].push_back(singleWire); + } + rows++; + } + } + + // finally add the forward extrusion offset wires + // these wires must be the last loft section + if (bFwd) { + rows = 0; + for (auto& wireVector : wiresections) { + for (auto& singleWire : wireVector) { + // count number of edges + numEdges = 0; + TopExp_Explorer xp(singleWire, TopAbs_EDGE); + while (xp.More()) { + numEdges++; + xp.Next(); + } + // create an offset copy of the wire + if (!isInnerWire[rows]) { + // this is an outer wire + createTaperedPrismOffset(TopoDS::Wire(singleWire), vecFwd, distanceFwd, numEdges, false, offsetWire); + } + else { + // there is an OCC bug with single-edge wires (circles), see inside createTaperedPrismOffset + if (numEdges > 1 || !isPartDesign) + // inner wires must get the negated offset + createTaperedPrismOffset(TopoDS::Wire(singleWire), vecFwd, -distanceFwd, numEdges, false, offsetWire); + else + // these wires must not get the negated offset + createTaperedPrismOffset(TopoDS::Wire(singleWire), vecFwd, distanceFwd, numEdges, false, offsetWire); + } + if (offsetWire.IsNull()) + return; + extrusionSections[rows].push_back(offsetWire); + } + ++rows; + } + } + + try { + // build all shells + std::vector shells; + + for (auto& wires : extrusionSections) { + BRepOffsetAPI_ThruSections mkTS(isSolid, /*ruled=*/Standard_True, Precision::Confusion()); + + for (auto& singleWire : wires) { + if (singleWire.ShapeType() == TopAbs_VERTEX) + mkTS.AddVertex(TopoDS::Vertex(singleWire)); + else + mkTS.AddWire(TopoDS::Wire(singleWire)); + } + mkTS.Build(); + if (!mkTS.IsDone()) + Standard_Failure::Raise("Extrusion: Loft could not be built"); + + shells.push_back(mkTS.Shape()); + } + + if (isSolid) { + // we only need to cut if we have inner wires + if (numInnerWires > 0) { + // we take every outer wire prism and cut subsequently all inner wires prisms from it + // every resulting shape is the final drafted extrusion shape + GProp_GProps tempProperties; + Standard_Real momentOfInertiaInitial; + Standard_Real momentOfInertiaFinal; + std::vector::iterator isInnerWireIterator = isInnerWire.begin(); + std::vector::iterator isInnerWireIteratorLoop; + for (auto itOuter = shells.begin(); itOuter != shells.end(); ++itOuter) { + if (*isInnerWireIterator == true) { + ++isInnerWireIterator; + continue; + } + isInnerWireIteratorLoop = isInnerWire.begin(); + for (auto itInner = shells.begin(); itInner != shells.end(); ++itInner) { + if (itOuter == itInner || *isInnerWireIteratorLoop == false) { + ++isInnerWireIteratorLoop; + continue; + } + // get MomentOfInertia of first shape + BRepGProp::VolumeProperties(*itOuter, tempProperties); + momentOfInertiaInitial = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), direction)); + BRepAlgoAPI_Cut mkCut(*itOuter, *itInner); + if (!mkCut.IsDone()) + Standard_Failure::Raise("Extrusion: Final cut out failed"); + BRepGProp::VolumeProperties(mkCut.Shape(), tempProperties); + momentOfInertiaFinal = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), direction)); + // if the whole shape was cut away the resulting shape is not Null but its MomentOfInertia is 0.0 + // therefore we have a valid cut if the MomentOfInertia is not zero and changed + if ((momentOfInertiaInitial != momentOfInertiaFinal) + && (momentOfInertiaFinal > Precision::Confusion())) { + // immediately update the outer shape since more inner wire prism might cut it + *itOuter = mkCut.Shape(); + } + ++isInnerWireIteratorLoop; + } + drafts.push_back(*itOuter); + ++isInnerWireIterator; + } + } + else + // we already have the results + for (auto it = shells.begin(); it != shells.end(); ++it) + drafts.push_back(*it); + } + else { // no solid + BRepBuilderAPI_Sewing sewer; + sewer.SetTolerance(Precision::Confusion()); + for (TopoDS_Shape& s : shells) + sewer.Add(s); + sewer.Perform(); + drafts.push_back(sewer.SewedShape()); + } + } + catch (Standard_Failure& e) { + throw Base::RuntimeError(e.GetMessageString()); + } + catch (const Base::Exception& e) { + throw Base::RuntimeError(e.what()); + } + catch (...) { + throw Base::CADKernelError("Extrusion: A fatal error occurred when making the loft"); + } +} + +void ExtrusionHelper::checkInnerWires(std::vector& isInnerWire, const gp_Dir direction, + std::vector&checklist, bool forInner, std::vector prisms) +{ + // store the number of wires to be checked + size_t numCheckWiresInitial = 0; + for (auto checks : checklist) { + if (checks) + ++numCheckWiresInitial; + } + GProp_GProps tempProperties; + Standard_Real momentOfInertiaInitial; + Standard_Real momentOfInertiaFinal; + size_t numCheckWires = 0; + std::vector::iterator isInnerWireIterator = isInnerWire.begin(); + std::vector::iterator toCheckIterator = checklist.begin(); + // create an array with false used later to store what can be cancelled from the checklist + std::vector toDisable(checklist.size(), false); + int outer = -1; + // we cut every prism to be checked from the other to be checked ones + // if nothing happens, a prism can be cancelled from the checklist + for (auto itOuter = prisms.begin(); itOuter != prisms.end(); ++itOuter) { + ++outer; + if (*toCheckIterator == false) { + ++isInnerWireIterator; + ++toCheckIterator; + continue; + } + auto toCheckIteratorInner = checklist.begin(); + bool saveIsInnerWireIterator = *isInnerWireIterator; + for (auto itInner = prisms.begin(); itInner != prisms.end(); ++itInner) { + if (itOuter == itInner || *toCheckIteratorInner == false) { + ++toCheckIteratorInner; + continue; + } + // get MomentOfInertia of first shape + BRepGProp::VolumeProperties(*itInner, tempProperties); + momentOfInertiaInitial = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), direction)); + BRepAlgoAPI_Cut mkCut(*itInner, *itOuter); + if (!mkCut.IsDone()) + Standard_Failure::Raise("Extrusion: Cut out failed"); + BRepGProp::VolumeProperties(mkCut.Shape(), tempProperties); + momentOfInertiaFinal = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), direction)); + // if the whole shape was cut away the resulting shape is not Null but its MomentOfInertia is 0.0 + // therefore we have an inner wire if the MomentOfInertia is not zero and changed + if ((momentOfInertiaInitial != momentOfInertiaFinal) + && (momentOfInertiaFinal > Precision::Confusion())) { + *isInnerWireIterator = !forInner; + ++numCheckWires; + *toCheckIterator = true; + break; + } + ++toCheckIteratorInner; + } + if (saveIsInnerWireIterator == *isInnerWireIterator) + // nothing was changed and we can remove it from the list to be checked + // but we cannot do this before the foor loop was fully run + toDisable[outer] = true; + ++isInnerWireIterator; + ++toCheckIterator; + } + + // cancel prisms from the checklist whose wire state did not change + size_t i = 0; + for (auto disable : toDisable) { + if (disable) + checklist[i] = false; + ++i; + } + + // if all wires are inner ones, we take the first one as outer and issue a warning + if (numCheckWires == isInnerWire.size()) { + isInnerWire[0] = false; + checklist[0] = false; + --numCheckWires; + Base::Console().Warning("Extrusion: could not determine what structure is the outer one.\n\ + The first input one will now be taken as outer one.\n"); + } + + // There can be cases with several wires all intersecting each other. + // Then it is impossible to find out what wire is an inner one + // and we can only treat all wires in the checklist as outer ones. + if (numCheckWiresInitial == numCheckWires) { + i = 0; + for (auto checks : checklist) { + if (checks) { + isInnerWire[i] = false; + checklist[i] = false; + --numCheckWires; + } + ++i; + } + Base::Console().Warning("Extrusion: too many self-intersection structures!\n\ + Impossible to determine what structure is an inner one.\n\ + All undeterminable structures will therefore be taken as outer ones.\n"); + } + + // recursively call the function until all wires are checked + if (numCheckWires > 1) + checkInnerWires(isInnerWire, direction, checklist, !forInner, prisms); +}; + +void ExtrusionHelper::createTaperedPrismOffset(TopoDS_Wire sourceWire, + const gp_Vec& translation, + double offset, + int numEdges, + bool isSecond, + TopoDS_Wire& result) { + + // if the wire consists of a single edge which has applied a placement + // then this placement must be reset because otherwise + // BRepOffsetAPI_MakeOffset shows weird behaviour by applying the placement, see + // https://dev.opencascade.org/content/brepoffsetapimakeoffset-wire-and-face-odd-occt-740 + gp_Trsf tempTransform; + tempTransform.SetTranslation(translation); + TopLoc_Location loc(tempTransform); + TopoDS_Wire movedSourceWire = TopoDS::Wire(sourceWire.Moved(loc)); + + TopoDS_Shape offsetShape; + if (fabs(offset) > Precision::Confusion()) { + TopLoc_Location edgeLocation; + if (numEdges == 1) { + // create a new wire from the input wire to determine its location + // to reset the location after the offet operation + BRepBuilderAPI_MakeWire mkWire; + TopExp_Explorer xp(sourceWire, TopAbs_EDGE); + while (xp.More()) { + TopoDS_Edge edge = TopoDS::Edge(xp.Current()); + edgeLocation = edge.Location(); + edge.Location(TopLoc_Location()); + mkWire.Add(edge); + xp.Next(); + } + movedSourceWire = mkWire.Wire(); + } + // create the offset shape + BRepOffsetAPI_MakeOffset mkOffset; + mkOffset.Init(GeomAbs_Arc); + mkOffset.Init(GeomAbs_Intersection); + mkOffset.AddWire(movedSourceWire); + try { + mkOffset.Perform(offset); + offsetShape = mkOffset.Shape(); + } + catch (const Base::Exception& e) { + throw Base::RuntimeError(e.what()); + result = TopoDS_Wire(); + } + if (!mkOffset.IsDone()) { + Standard_Failure::Raise("Extrusion: Offset could not be created"); + result = TopoDS_Wire(); + } + if (numEdges == 1) { + // we need to move the offset wire first back to its original position + offsetShape.Move(edgeLocation); + // now apply the translation + offsetShape = offsetShape.Moved(loc); + } + } + else { + offsetShape = movedSourceWire; + } + if (offsetShape.IsNull()) { + if (isSecond) + Base::Console().Error("Extrusion: end face of tapered against extrusion is empty\n" \ + "This means most probably that the against taper angle is too large or small.\n"); + else + Base::Console().Error("Extrusion: end face of tapered along extrusion is empty\n" \ + "This means most probably that the along taper angle is too large or small.\n"); + Standard_Failure::Raise("Extrusion: end face of tapered extrusion is empty"); + } + // assure we return a wire and no edge + TopAbs_ShapeEnum type = offsetShape.ShapeType(); + if (type == TopAbs_WIRE) { + result = TopoDS::Wire(offsetShape); + } + else if (type == TopAbs_EDGE) { + BRepBuilderAPI_MakeWire mkWire2(TopoDS::Edge(offsetShape)); + result = mkWire2.Wire(); + } + else { + // this happens usually if type == TopAbs_COMPOUND and means the angle is too small + // since this is a common mistake users will quickly do, issue a warning dialog + // FIXME: Standard_Failure::Raise or App::DocumentObjectExecReturn don't output the message to the user + result = TopoDS_Wire(); + if (isSecond) + Base::Console().Error("Extrusion: type of against extrusion end face is not supported.\n" \ + "This means most probably that the against taper angle is too large or small.\n"); + else + Base::Console().Error("Extrusion: type of along extrusion is not supported.\n" \ + "This means most probably that the along taper angle is too large or small.\n"); + } + +} diff --git a/src/Mod/Part/App/ExtrusionHelper.h b/src/Mod/Part/App/ExtrusionHelper.h new file mode 100644 index 0000000000..cbb6114f09 --- /dev/null +++ b/src/Mod/Part/App/ExtrusionHelper.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (c) 2022 Uwe Stöhr * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef PART_EXTRUSIONHELPER_H +#define PART_EXTRUSIONHELPER_H + +#include +#include +#include + +namespace Part +{ + +class PartExport ExtrusionHelper +{ +public: + ExtrusionHelper(); + + /** + * @brief makeDraft: creates a drafted extrusion shape out of the input 2D shape + */ + static void makeDraft(const TopoDS_Shape& shape, + const gp_Dir& direction, + const double LengthFwd, + const double LengthRev, + const double AngleFwd, + const double AngleRev, + bool isSolid, + std::list& drafts, + bool isPartDesign); + /** + * @brief checkInnerWires: Checks what wires are inner ones by taking a set of prisms created with every wire. + * The prisms are cut from each other. If the moment of inertia thereby changes, the prism wire is an inner wire. + * Inner wires can have nested inner wires that are then in fact outer wires. + * Therefore checkInnerWires is called recursively until all wires are checked. + */ + static void checkInnerWires(std::vector& isInnerWire, const gp_Dir direction, + std::vector& checklist, bool forInner, std::vector prisms); + /** + * @brief createTaperedPrismOffset: creates an offset wire from the sourceWire in the specified + * translation. isSecond determines if the wire is used for the 2nd extrusion direction. + */ + static void createTaperedPrismOffset(TopoDS_Wire sourceWire, + const gp_Vec& translation, + double offset, + int numEdges, + bool isSecond, + TopoDS_Wire& result); +}; + +} //namespace Part + +#endif // PART_EXTRUSIONHELPER_H diff --git a/src/Mod/Part/App/FeatureExtrusion.cpp b/src/Mod/Part/App/FeatureExtrusion.cpp index 183b4d7558..f4df79ffe4 100644 --- a/src/Mod/Part/App/FeatureExtrusion.cpp +++ b/src/Mod/Part/App/FeatureExtrusion.cpp @@ -28,19 +28,12 @@ # include # include # include -# include -# include # include -# include # include -# include # include -# include -# include # include # include # include -# include # include # include # include @@ -55,6 +48,7 @@ #include #include #include +#include "ExtrusionHelper.h" #include "Part2DObject.h" using namespace Part; @@ -260,7 +254,9 @@ TopoShape Extrusion::extrudeShape(const TopoShape& source, const Extrusion::Extr myShape = BRepBuilderAPI_Copy(myShape).Shape(); std::list drafts; - makeDraft(params, myShape, drafts); + bool isPartDesign = false; // there is an OCC bug with single-edge wires (circles) we need to treat differently for PD and Part + ExtrusionHelper::makeDraft(myShape, params.dir, params.lengthFwd, params.lengthRev, + params.taperAngleFwd, params.taperAngleRev, params.solid, drafts, isPartDesign); if (drafts.empty()) { Standard_Failure::Raise("Drafting shape failed"); } @@ -339,434 +335,6 @@ App::DocumentObjectExecReturn* Extrusion::execute(void) } } -void Extrusion::makeDraft(const ExtrusionParameters& params, const TopoDS_Shape& shape, std::list& drafts) -{ - std::vector> wiresections; - - auto addWiresToWireSections = - [&shape](std::vector>& wiresections) -> size_t { - TopExp_Explorer ex; - size_t i = 0; - for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { - wiresections.push_back(std::vector()); - wiresections[i].push_back(TopoDS::Wire(ex.Current())); - } - return i; - }; - - double distanceFwd = tan(params.taperAngleFwd)*params.lengthFwd; - double distanceRev = tan(params.taperAngleRev)*params.lengthRev; - gp_Vec vecFwd = gp_Vec(params.dir) * params.lengthFwd; - gp_Vec vecRev = gp_Vec(params.dir.Reversed()) * params.lengthRev; - - bool bFwd = fabs(params.lengthFwd) > Precision::Confusion(); - bool bRev = fabs(params.lengthRev) > Precision::Confusion(); - // only if there is a 2nd direction and the negated angle is equal to the first one - // we can omit the source shape as loft section - bool bMid = !bFwd || !bRev || -1.0 * params.taperAngleFwd != params.taperAngleRev; - - if (shape.IsNull()) - Standard_Failure::Raise("Not a valid shape"); - - // store all wires of the shape into an array - size_t numWires = addWiresToWireSections(wiresections); - if (numWires == 0) - Standard_Failure::Raise("Extrusion: Input must not only consist if a vertex"); - - // to store the sections for the loft - std::list list_of_sections; - - // we need for all found wires an offset copy of them - // we store them in an array - TopoDS_Wire offsetWire; - std::vector> extrusionSections(wiresections.size(), std::vector()); - size_t rows = 0; - int numEdges = 0; - - // We need to find out what are outer wires and what are inner ones - // methods like checking the center of mass etc. don't help us here. - // As solution we build a prism with every wire, then subtract every prism from each other. - // If the moment of inertia changes by a subtraction, we have an inner wire prism. - // - // first build the prisms - std::vector resultPrisms; - TopoDS_Shape singlePrism; - for (auto& wireVector : wiresections) { - for (auto& singleWire : wireVector) { - BRepBuilderAPI_MakeFace mkFace(TopoDS::Wire(singleWire)); - auto tempFace = mkFace.Shape(); - BRepPrimAPI_MakePrism mkPrism(tempFace, vecFwd); - if(!mkPrism.IsDone()) - Standard_Failure::Raise("Extrusion: Generating prism failed"); - singlePrism = mkPrism.Shape(); - resultPrisms.push_back(singlePrism); - } - } - // create an array with false to store later which wires are inner ones - std::vector isInnerWire(resultPrisms.size(), false); - std::vector checklist(resultPrisms.size(), true); - // finally check reecursively for inner wires - checkInnerWires(isInnerWire, params.dir, checklist, false, resultPrisms); - - // count the number of inner wires - int numInnerWires = 0; - for (auto isInner : isInnerWire) { - if (isInner) - ++numInnerWires; - } - - // at first create offset wires for the reversed part of extrusion - // it is important that these wires are the first loft section - if (bRev) { - // create an offset for all source wires - rows = 0; - for (auto& wireVector : wiresections) { - for (auto& singleWire : wireVector) { - // count number of edges - numEdges = 0; - TopExp_Explorer xp(singleWire, TopAbs_EDGE); - while (xp.More()) { - numEdges++; - xp.Next(); - } - // create an offset copy of the wire - if (!isInnerWire[rows]) { - // this is an outer wire - createTaperedPrismOffset(TopoDS::Wire(singleWire), vecRev, distanceRev, numEdges, true, offsetWire); - } - else { - // inner wires must be reversed and get the negated offset - createTaperedPrismOffset(TopoDS::Wire(singleWire.Reversed()), vecRev, -distanceRev, numEdges, true, offsetWire); - } - if (offsetWire.IsNull()) - return; - extrusionSections[rows].push_back(offsetWire); - } - ++rows; - } - } - - // add the source wire as middle section - // it is important to add them after the reversed part - if (bMid) { - // transfer all source wires as they are to the array from which we build the shells - rows = 0; - for (auto& wireVector : wiresections) { - for (auto& singleWire : wireVector) { - extrusionSections[rows].push_back(singleWire); - } - rows++; - } - } - - // finally add the forward extrusion offset wires - // these wires must be the last loft section - if (bFwd) { - rows = 0; - for (auto& wireVector : wiresections) { - for (auto& singleWire : wireVector) { - // count number of edges - numEdges = 0; - TopExp_Explorer xp(singleWire, TopAbs_EDGE); - while (xp.More()) { - numEdges++; - xp.Next(); - } - // create an offset copy of the wire - if (!isInnerWire[rows]) { - // this is an outer wire - createTaperedPrismOffset(TopoDS::Wire(singleWire), vecFwd, distanceFwd, numEdges, false, offsetWire); - } - else { - // inner wires must be reversed and get the negated offset - createTaperedPrismOffset(TopoDS::Wire(singleWire.Reversed()), vecFwd, -distanceFwd, numEdges, false, offsetWire); - } - if (offsetWire.IsNull()) - return; - extrusionSections[rows].push_back(offsetWire); - } - ++rows; - } - } - - try { - // build all shells - std::vector shells; - - for (auto& wires : extrusionSections) { - BRepOffsetAPI_ThruSections mkTS(params.solid, /*ruled=*/Standard_True, Precision::Confusion()); - - for (auto& singleWire : wires) { - if (singleWire.ShapeType() == TopAbs_VERTEX) - mkTS.AddVertex(TopoDS::Vertex(singleWire)); - else - mkTS.AddWire(TopoDS::Wire(singleWire)); - } - mkTS.Build(); - if (!mkTS.IsDone()) - Standard_Failure::Raise("Extrusion: Loft could not be built"); - - shells.push_back(mkTS.Shape()); - } - - if (params.solid) { - // we only need to cut if we have inner wires - if (numInnerWires > 0) { - // we take every outer wire prism and cut subsequently all inner wires prisms from it - // every resulting shape is the final drafted extrusion shape - GProp_GProps tempProperties; - Standard_Real momentOfInertiaInitial; - Standard_Real momentOfInertiaFinal; - std::vector::iterator isInnerWireIterator = isInnerWire.begin(); - std::vector::iterator isInnerWireIteratorLoop; - for (auto itOuter = shells.begin(); itOuter != shells.end(); ++itOuter) { - if (*isInnerWireIterator == true) { - ++isInnerWireIterator; - continue; - } - isInnerWireIteratorLoop = isInnerWire.begin(); - for (auto itInner = shells.begin(); itInner != shells.end(); ++itInner) { - if (itOuter == itInner || *isInnerWireIteratorLoop == false) { - ++isInnerWireIteratorLoop; - continue; - } - // get MomentOfInertia of first shape - BRepGProp::VolumeProperties(*itOuter, tempProperties); - momentOfInertiaInitial = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), params.dir)); - BRepAlgoAPI_Cut mkCut(*itOuter, *itInner); - if (!mkCut.IsDone()) - Standard_Failure::Raise("Extrusion: Final cut out failed"); - BRepGProp::VolumeProperties(mkCut.Shape(), tempProperties); - momentOfInertiaFinal = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), params.dir)); - // if the whole shape was cut away the resulting shape is not Null but its MomentOfInertia is 0.0 - // therefore we have a valid cut if the MomentOfInertia is not zero and changed - if ((momentOfInertiaInitial != momentOfInertiaFinal) - && (momentOfInertiaFinal > Precision::Confusion())) { - // immediately update the outer shape since more inner wire prism might cut it - *itOuter = mkCut.Shape(); - } - ++isInnerWireIteratorLoop; - } - drafts.push_back(*itOuter); - ++isInnerWireIterator; - } - } else - // we already have the results - for (auto it = shells.begin(); it != shells.end(); ++it) - drafts.push_back(*it); - } - else { // no solid - BRepBuilderAPI_Sewing sewer; - sewer.SetTolerance(Precision::Confusion()); - for (TopoDS_Shape& s : shells) - sewer.Add(s); - sewer.Perform(); - drafts.push_back(sewer.SewedShape()); - } - } - catch (Standard_Failure& e) { - throw Base::RuntimeError(e.GetMessageString()); - } - catch (const Base::Exception& e) { - throw Base::RuntimeError(e.what()); - } - catch (...) { - throw Base::CADKernelError("Extrusion: A fatal error occurred when making the loft"); - } -} - -void Extrusion::checkInnerWires(std::vector& isInnerWire, const gp_Dir direction, - std::vector& checklist, bool forInner, std::vector prisms) -{ - // store the number of wires to be checked - size_t numCheckWiresInitial = 0; - for (auto checks : checklist) { - if (checks) - ++numCheckWiresInitial; - } - GProp_GProps tempProperties; - Standard_Real momentOfInertiaInitial; - Standard_Real momentOfInertiaFinal; - size_t numCheckWires = 0; - std::vector::iterator isInnerWireIterator = isInnerWire.begin(); - std::vector::iterator toCheckIterator = checklist.begin(); - // create an array with false used later to store what can be cancelled from the checklist - std::vector toDisable(checklist.size(), false); - int outer = -1; - // we cut every prism to be checked from the other to be checked ones - // if nothing happens, a prism can be cancelled from the checklist - for (auto itOuter = prisms.begin(); itOuter != prisms.end(); ++itOuter) { - ++outer; - if (*toCheckIterator == false) { - ++isInnerWireIterator; - ++toCheckIterator; - continue; - } - auto toCheckIteratorInner = checklist.begin(); - bool saveIsInnerWireIterator = *isInnerWireIterator; - for (auto itInner = prisms.begin(); itInner != prisms.end(); ++itInner) { - if (itOuter == itInner || *toCheckIteratorInner == false) { - ++toCheckIteratorInner; - continue; - } - // get MomentOfInertia of first shape - BRepGProp::VolumeProperties(*itInner, tempProperties); - momentOfInertiaInitial = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), direction)); - BRepAlgoAPI_Cut mkCut(*itInner, *itOuter); - if (!mkCut.IsDone()) - Standard_Failure::Raise("Extrusion: Cut out failed"); - BRepGProp::VolumeProperties(mkCut.Shape(), tempProperties); - momentOfInertiaFinal = tempProperties.MomentOfInertia(gp_Ax1(gp_Pnt(), direction)); - // if the whole shape was cut away the resulting shape is not Null but its MomentOfInertia is 0.0 - // therefore we have an inner wire if the MomentOfInertia is not zero and changed - if ((momentOfInertiaInitial != momentOfInertiaFinal) - && (momentOfInertiaFinal > Precision::Confusion())) { - *isInnerWireIterator = !forInner; - ++numCheckWires; - *toCheckIterator = true; - break; - } - ++toCheckIteratorInner; - } - if (saveIsInnerWireIterator == *isInnerWireIterator) - // nothing was changed and we can remove it from the list to be checked - // but we cannot do this before the foor loop was fully run - toDisable[outer] = true; - ++isInnerWireIterator; - ++toCheckIterator; - } - - // cancel prisms from the checklist whose wire state did not change - size_t i = 0; - for (auto disable : toDisable) { - if (disable) - checklist[i] = false; - ++i; - } - - // if all wires are inner ones, we take the first one as outer and issue a warning - if (numCheckWires == isInnerWire.size()) { - isInnerWire[0] = false; - checklist[0] = false; - --numCheckWires; - Base::Console().Warning("Extrusion: could not determine what structure is the outer one.\n\ - The first input one will now be taken as outer one.\n"); - } - - // There can be cases with several wires all intersecting each other. - // Then it is impossible to find out what wire is an inner one - // and we can only treat all wires in the checklist as outer ones. - if (numCheckWiresInitial == numCheckWires) { - i = 0; - for (auto checks : checklist) { - if (checks) { - isInnerWire[i] = false; - checklist[i] = false; - --numCheckWires; - } - ++i; - } - Base::Console().Warning("Extrusion: too many self-intersection structures!\n\ - Impossible to determine what structure is an inner one.\n\ - All undeterminable structures will therefore be taken as outer ones.\n"); - } - - // recursively call the function until all wires are checked - if (numCheckWires > 1) - checkInnerWires(isInnerWire, direction, checklist, !forInner, prisms); -}; - -void Extrusion::createTaperedPrismOffset(TopoDS_Wire sourceWire, - const gp_Vec& translation, - double offset, - int numEdges, - bool isSecond, - TopoDS_Wire& result) { - - // if the wire consists of a single edge which has applied a placement - // then this placement must be reset because otherwise - // BRepOffsetAPI_MakeOffset shows weird behaviour by applying the placement - gp_Trsf tempTransform; - tempTransform.SetTranslation(translation); - TopLoc_Location loc(tempTransform); - TopoDS_Wire movedSourceWire = TopoDS::Wire(sourceWire.Moved(loc)); - - TopoDS_Shape offsetShape; - if (fabs(offset) > Precision::Confusion()) { - TopLoc_Location edgeLocation; - if (numEdges == 1) { - // create a new wire from the input wire to determine its location - // to reset the location after the offet operation - BRepBuilderAPI_MakeWire mkWire; - TopExp_Explorer xp(sourceWire, TopAbs_EDGE); - while (xp.More()) { - TopoDS_Edge edge = TopoDS::Edge(xp.Current()); - edgeLocation = edge.Location(); - edge.Location(TopLoc_Location()); - mkWire.Add(edge); - xp.Next(); - } - movedSourceWire = mkWire.Wire(); - } - // create the offset shape - BRepOffsetAPI_MakeOffset mkOffset; - mkOffset.Init(GeomAbs_Arc); - mkOffset.Init(GeomAbs_Intersection); - mkOffset.AddWire(movedSourceWire); - try { - mkOffset.Perform(offset); - offsetShape = mkOffset.Shape(); - } - catch (const Base::Exception& e) { - throw Base::RuntimeError(e.what()); - result = TopoDS_Wire(); - } - if (!mkOffset.IsDone()) { - Standard_Failure::Raise("Extrusion: Offset could not be created"); - result = TopoDS_Wire(); - } - if (numEdges == 1) { - // we need to move the offset wire first back to its original position - offsetShape.Move(edgeLocation); - // now apply the translation - offsetShape = offsetShape.Moved(loc); - } - } - else { - offsetShape = movedSourceWire; - } - if (offsetShape.IsNull()) { - if (isSecond) - Base::Console().Error("Extrusion: end face of tapered against extrusion is empty\n" \ - "This means most probably that the against taper angle is too large or small.\n"); - else - Base::Console().Error("Extrusion: end face of tapered along extrusion is empty\n" \ - "This means most probably that the along taper angle is too large or small.\n"); - Standard_Failure::Raise("Extrusion: end face of tapered extrusion is empty"); - } - // assure we return a wire and no edge - TopAbs_ShapeEnum type = offsetShape.ShapeType(); - if (type == TopAbs_WIRE) { - result = TopoDS::Wire(offsetShape); - } - else if (type == TopAbs_EDGE) { - BRepBuilderAPI_MakeWire mkWire2(TopoDS::Edge(offsetShape)); - result = mkWire2.Wire(); - } - else { - // this happens usually if type == TopAbs_COMPOUND and means the angle is too small - // since this is a common mistake users will quickly do, issue a warning dialog - // FIXME: Standard_Failure::Raise or App::DocumentObjectExecReturn don't output the message to the user - result = TopoDS_Wire(); - if (isSecond) - Base::Console().Error("Extrusion: type of against extrusion end face is not supported.\n" \ - "This means most probably that the against taper angle is too large or small.\n"); - else - Base::Console().Error("Extrusion: type of along extrusion is not supported.\n" \ - "This means most probably that the along taper angle is too large or small.\n"); - } -} - //---------------------------------------------------------------- TYPESYSTEM_SOURCE(Part::FaceMakerExtrusion, Part::FaceMakerCheese) diff --git a/src/Mod/Part/App/FeatureExtrusion.h b/src/Mod/Part/App/FeatureExtrusion.h index 12d9819f2c..95062f1061 100644 --- a/src/Mod/Part/App/FeatureExtrusion.h +++ b/src/Mod/Part/App/FeatureExtrusion.h @@ -120,29 +120,6 @@ public: //mode enumerations }; static const char* eDirModeStrings[]; -protected: - static void makeDraft(const ExtrusionParameters& params, const TopoDS_Shape&, std::list&); - - /** - * @brief checkInnerWires: Checks what wires are inner ones by taking a set of prisms created with every wire. - * The prisms are cut from each other. If the moment of inertia thereby changes, the prism wire is an inner wire. - * Inner wires can have nested inner wires that are then in fact outer wires. - * Therefore checkInnerWires is called recursively until all wires are checked. - */ - static void checkInnerWires(std::vector& isInnerWire, const gp_Dir direction, - std::vector& checklist, bool forInner, std::vector prisms); - - /** - * @brief createTaperedPrismOffset: creates an offset wire from the sourceWire in the specified - * translation. isSecond determines if the wire is used for the 2nd extrusion direction. - */ - static void createTaperedPrismOffset(TopoDS_Wire sourceWire, - const gp_Vec& translation, - double offset, - int numEdges, - bool isSecond, - TopoDS_Wire& result); - protected: virtual void setupObject() override; }; diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index 4330551f04..e902e6504d 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -51,15 +51,17 @@ #include #include #include +#include #include "FeatureExtrude.h" using namespace PartDesign; - PROPERTY_SOURCE(PartDesign::FeatureExtrude, PartDesign::ProfileBased) App::PropertyQuantityConstraint::Constraints FeatureExtrude::signedLengthConstraint = { -DBL_MAX, DBL_MAX, 1.0 }; +double FeatureExtrude::maxAngle = 90 - Base::toDegrees(Precision::Angular()); +App::PropertyAngle::Constraints FeatureExtrude::floatAngle = { -maxAngle, maxAngle, 1.0 }; FeatureExtrude::FeatureExtrude() { @@ -71,6 +73,8 @@ short FeatureExtrude::mustExecute() const Type.isTouched() || Length.isTouched() || Length2.isTouched() || + TaperAngle.isTouched() || + TaperAngle2.isTouched() || UseCustomVector.isTouched() || Direction.isTouched() || ReferenceAxis.isTouched() || diff --git a/src/Mod/PartDesign/App/FeatureExtrude.h b/src/Mod/PartDesign/App/FeatureExtrude.h index 122d68bd1e..bb77306272 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.h +++ b/src/Mod/PartDesign/App/FeatureExtrude.h @@ -24,8 +24,10 @@ #ifndef PARTDESIGN_FEATURE_EXTRUDE_H #define PARTDESIGN_FEATURE_EXTRUDE_H +#include #include #include +#include #include "FeatureSketchBased.h" namespace PartDesign @@ -41,6 +43,8 @@ public: App::PropertyEnumeration Type; App::PropertyLength Length; App::PropertyLength Length2; + App::PropertyAngle TaperAngle; + App::PropertyAngle TaperAngle2; App::PropertyBool UseCustomVector; App::PropertyVector Direction; App::PropertyBool AlongSketchNormal; @@ -48,6 +52,8 @@ public: App::PropertyLinkSub ReferenceAxis; static App::PropertyQuantityConstraint::Constraints signedLengthConstraint; + static double maxAngle; + static App::PropertyAngle::Constraints floatAngle; /** @name methods override feature */ //@{ diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index ce5c1dd546..43f4b7262f 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -75,6 +75,10 @@ Pad::Pad() ADD_PROPERTY_TYPE(UpToFace, (0), "Pad", App::Prop_None, "Face where pad will end"); ADD_PROPERTY_TYPE(Offset, (0.0), "Pad", App::Prop_None, "Offset from face in which pad will end"); Offset.setConstraints(&signedLengthConstraint); + ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Pad", App::Prop_None, "Taper angle"); + TaperAngle.setConstraints(&floatAngle); + ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Pad", App::Prop_None, "Taper angle for 2nd direction"); + TaperAngle2.setConstraints(&floatAngle); // Remove the constraints and keep the type to allow to accept negative values // https://forum.freecadweb.org/viewtopic.php?f=3&t=52075&p=448410#p447636 @@ -186,7 +190,7 @@ App::DocumentObjectExecReturn *Pad::execute() supportface = TopoDS_Face(); PrismMode mode = PrismMode::None; - generatePrism(prism, method, base, sketchshape, supportface, upToFace, dir, mode, Standard_True); + Extrude(prism, method, base, sketchshape, supportface, upToFace, dir, mode, Standard_True); base.Nullify(); } else { @@ -203,12 +207,12 @@ App::DocumentObjectExecReturn *Pad::execute() if (!Ex.More()) supportface = TopoDS_Face(); PrismMode mode = PrismMode::None; - generatePrism(prism, method, base, sketchshape, supportface, upToFace, dir, mode, Standard_True); + Extrude(prism, method, base, sketchshape, supportface, upToFace, dir, mode, Standard_True); } } else { - generatePrism(prism, sketchshape, method, dir, L, L2, - hasMidplane, hasReversed); + Extrude(prism, sketchshape, method, dir, L, L2, + TaperAngle.getValue(), TaperAngle2.getValue(), hasMidplane, hasReversed); } if (prism.IsNull()) diff --git a/src/Mod/PartDesign/App/FeaturePocket.cpp b/src/Mod/PartDesign/App/FeaturePocket.cpp index 5e1a131ae7..1c0e11346f 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.cpp +++ b/src/Mod/PartDesign/App/FeaturePocket.cpp @@ -74,6 +74,10 @@ Pocket::Pocket() ADD_PROPERTY_TYPE(UpToFace, (0), "Pocket", App::Prop_None, "Face where pocket will end"); ADD_PROPERTY_TYPE(Offset, (0.0), "Pocket", App::Prop_None, "Offset from face in which pocket will end"); Offset.setConstraints(&signedLengthConstraint); + ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Pocket", App::Prop_None, "Taper angle"); + TaperAngle.setConstraints(&floatAngle); + ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Pocket", App::Prop_None, "Taper angle for 2nd direction"); + TaperAngle2.setConstraints(&floatAngle); // Remove the constraints and keep the type to allow to accept negative values // https://forum.freecadweb.org/viewtopic.php?f=3&t=52075&p=448410#p447636 @@ -196,7 +200,7 @@ 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); + Extrude(prism, method, base, profileshape, supportface, upToFace, dir, mode, Standard_True); // And the really expensive way to get the SubShape... BRepAlgoAPI_Cut mkCut(base, prism); @@ -215,8 +219,8 @@ App::DocumentObjectExecReturn *Pocket::execute() } else { TopoDS_Shape prism; - generatePrism(prism, profileshape, method, dir, L, L2, - Midplane.getValue(), Reversed.getValue()); + Extrude(prism, profileshape, method, dir, L, L2, TaperAngle.getValue(), TaperAngle2.getValue(), + Midplane.getValue(), Reversed.getValue()); if (prism.IsNull()) return new App::DocumentObjectExecReturn("Pocket: Resulting shape is empty"); diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 531c40c419..e824697aae 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -35,6 +35,7 @@ # include # include # include +# include # include # include # include @@ -42,6 +43,8 @@ # include # include # include +# include +# include # include # include # include @@ -73,9 +76,11 @@ #include #include #include +#include #include #include #include +#include #include #include "FeatureSketchBased.h" #include "DatumPlane.h" @@ -580,18 +585,21 @@ double ProfileBased::getThroughAllLength() const return 2.02 * sqrt(box.SquareExtent()); } -void ProfileBased::generatePrism(TopoDS_Shape& prism, - const TopoDS_Shape& sketchshape, - const std::string& method, - const gp_Dir& dir, - const double L, - const double L2, - const bool midplane, - const bool reversed) +void ProfileBased::Extrude(TopoDS_Shape& prism, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& direction, + const double L, + const double L2, + const double angle, + const double angle2, + const bool midplane, + const bool reversed) { if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { double Ltotal = L; double Loffset = 0.; + gp_Dir directionTaper = direction; if (method == "ThroughAll") Ltotal = getThroughAllLength(); @@ -613,7 +621,7 @@ void ProfileBased::generatePrism(TopoDS_Shape& prism, TopoDS_Shape from = sketchshape; if (method == "TwoLengths" || midplane) { gp_Trsf mov; - mov.SetTranslation(Loffset * gp_Vec(dir)); + mov.SetTranslation(Loffset * gp_Vec(direction)); TopLoc_Location loc(mov); from = sketchshape.Moved(loc); } @@ -628,12 +636,25 @@ void ProfileBased::generatePrism(TopoDS_Shape& prism, throw Base::ValueError("Cannot create a pocket with a depth of zero."); } - // Its better not to use BRepFeat_MakePrism here even if we have a support because the - // resulting shape creates problems with Pocket - BRepPrimAPI_MakePrism PrismMaker(from, Ltotal * gp_Vec(dir), 0, 1); // finite prism - if (!PrismMaker.IsDone()) - throw Base::RuntimeError("ProfileBased: Length: Could not extrude the sketch!"); - prism = PrismMaker.Shape(); + // now we can create either a tapered or linear prism. + // If tapered, we create is using Part's draft extrusion method. If linear we create a prism. + if (fabs(angle) > Base::toRadians(Precision::Angular()) || fabs(angle2) > Base::toRadians(Precision::Angular())) { + // prism is tapered + if (reversed) + directionTaper.Reverse(); + generateTaperedPrism(prism, sketchshape, method, directionTaper, L, L2, angle, angle2, midplane); + } + else { + // Without taper angle we create a prism because its shells are in every case no B-splines and can therefore + // be use as support for further features like Pads, Lofts etc. B-spline shells can break certain features, + // see e.g. https://forum.freecadweb.org/viewtopic.php?p=560785#p560785 + // It is better not to use BRepFeat_MakePrism here even if we have a support because the + // resulting shape creates problems with Pocket + BRepPrimAPI_MakePrism PrismMaker(from, Ltotal * gp_Vec(direction), 0, 1); // finite prism + if (!PrismMaker.IsDone()) + throw Base::RuntimeError("ProfileBased: Length: Could not extrude the sketch!"); + prism = PrismMaker.Shape(); + } } else { std::stringstream str; @@ -641,18 +662,17 @@ void ProfileBased::generatePrism(TopoDS_Shape& prism, << method << "' for generatePrism()"; throw Base::RuntimeError(str.str()); } - } -void ProfileBased::generatePrism(TopoDS_Shape& prism, - const std::string& method, - const TopoDS_Shape& baseshape, - const TopoDS_Shape& profileshape, - const TopoDS_Face& supportface, - const TopoDS_Face& uptoface, - const gp_Dir& direction, - PrismMode Mode, - Standard_Boolean Modify) +void ProfileBased::Extrude(TopoDS_Shape& prism, + const std::string& method, + const TopoDS_Shape& baseshape, + const TopoDS_Shape& profileshape, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const gp_Dir& direction, + PrismMode Mode, + Standard_Boolean Modify) { if (method == "UpToFirst" || method == "UpToFace" || method == "UpToLast") { BRepFeat_MakePrism PrismMaker; @@ -678,6 +698,51 @@ void ProfileBased::generatePrism(TopoDS_Shape& prism, } } +void ProfileBased::generateTaperedPrism(TopoDS_Shape& prism, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& direction, + const double L, + const double L2, + const double angle, + const double angle2, + const bool midplane) +{ + std::list drafts; + bool isSolid = true; // in PD we only generate solids, while Part Extrude can also create only shells + bool isPartDesign = true; // there is an OCC bug with single-edge wires (circles) we need to treat differently for PD and Part + if (method == "ThroughAll") + Part::ExtrusionHelper::makeDraft(sketchshape, direction, getThroughAllLength(), + 0.0, Base::toRadians(angle), 0.0, isSolid, drafts, isPartDesign); + else if (method == "TwoLengths") + Part::ExtrusionHelper::makeDraft(sketchshape, direction, L, L2, + Base::toRadians(angle), Base::toRadians(angle2), isSolid, drafts, isPartDesign); + else if (method == "Length") { + if (midplane) { + Part::ExtrusionHelper::makeDraft(sketchshape, direction, L / 2, L / 2, + Base::toRadians(angle), Base::toRadians(angle), isSolid, drafts, isPartDesign); + } + else + Part::ExtrusionHelper::makeDraft(sketchshape, direction, L, 0.0, + Base::toRadians(angle), 0.0, isSolid, drafts, isPartDesign); + } + + if (drafts.empty()) { + throw Base::RuntimeError("Creation of tapered object failed"); + } + else if (drafts.size() == 1) { + prism = drafts.front(); + } + else { + TopoDS_Compound comp; + BRep_Builder builder; + builder.MakeCompound(comp); + for (std::list::iterator it = drafts.begin(); it != drafts.end(); ++it) + builder.Add(comp, *it); + prism = comp; + } +} + bool ProfileBased::checkWireInsideFace(const TopoDS_Wire& wire, const TopoDS_Face& face, const gp_Dir& dir) { // Project wire onto the face (face, not surface! So limits of face apply) @@ -1034,7 +1099,6 @@ bool ProfileBased::isParallelPlane(const TopoDS_Shape & s1, const TopoDS_Shape & return false; } - double ProfileBased::getReversedAngle(const Base::Vector3d & b, const Base::Vector3d & v) { try { diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index 3e3522e156..a4ba6cb7f1 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -143,28 +143,31 @@ protected: double offset); /** - * Generate a linear prism - * It will be a stand-alone solid created with BRepPrimAPI_MakePrism + * Generates an extrusion of the input sketchshape and stores it in the given &prism */ - void generatePrism(TopoDS_Shape& prism, - const TopoDS_Shape& sketchshape, - const std::string& method, - const gp_Dir& direction, - const double L, - const double L2, - const bool midplane, - const bool reversed); + void Extrude(TopoDS_Shape& prism, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& direction, + const double L, + const double L2, + const double angle, + const double angle2, + const bool midplane, + const bool reversed); + // See BRepFeat_MakePrism enum PrismMode { CutFromBase = 0, FuseWithBase = 1, None = 2 }; + /** - * Generate a linear prism + * Generates an extrusion of the input profileshape * It will be a stand-alone solid created with BRepFeat_MakePrism */ - static void generatePrism(TopoDS_Shape& prism, + static void Extrude(TopoDS_Shape& prism, const std::string& method, const TopoDS_Shape& baseshape, const TopoDS_Shape& profileshape, @@ -174,6 +177,19 @@ protected: PrismMode Mode, Standard_Boolean Modify); + /** + * Generates a tapered prism of the input sketchshape and stores it in the given &prism + */ + void generateTaperedPrism(TopoDS_Shape& prism, + const TopoDS_Shape& sketchshape, + const std::string& method, + const gp_Dir& direction, + const double L, + const double L2, + const double angle, + const double angle2, + const bool midplane); + /// Check whether the wire after projection on the face is inside the face static bool checkWireInsideFace(const TopoDS_Wire& wire, const TopoDS_Face& face, diff --git a/src/Mod/PartDesign/App/PreCompiled.h b/src/Mod/PartDesign/App/PreCompiled.h index 47354e19d1..7454b6fce7 100644 --- a/src/Mod/PartDesign/App/PreCompiled.h +++ b/src/Mod/PartDesign/App/PreCompiled.h @@ -82,6 +82,7 @@ # include # include # include +# include # include # include # include @@ -89,6 +90,7 @@ # include # include # include +# include # include # include # include diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp index ae4c3ba2f8..5a13685baf 100644 --- a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp @@ -73,6 +73,8 @@ void TaskExtrudeParameters::setupDialog() Base::Quantity l = extrude->Length.getQuantityValue(); Base::Quantity l2 = extrude->Length2.getQuantityValue(); Base::Quantity off = extrude->Offset.getQuantityValue(); + Base::Quantity taper = extrude->TaperAngle.getQuantityValue(); + Base::Quantity taper2 = extrude->TaperAngle2.getQuantityValue(); bool alongNormal = extrude->AlongSketchNormal.getValue(); bool useCustom = extrude->UseCustomVector.getValue(); @@ -107,6 +109,14 @@ void TaskExtrudeParameters::setupDialog() ui->lengthEdit->setValue(l); ui->lengthEdit2->setValue(l2); ui->offsetEdit->setValue(off); + ui->taperEdit->setMinimum(extrude->TaperAngle.getMinimum()); + ui->taperEdit->setMaximum(extrude->TaperAngle.getMaximum()); + ui->taperEdit->setSingleStep(extrude->TaperAngle.getStepSize()); + ui->taperEdit->setValue(taper); + ui->taperEdit2->setMinimum(extrude->TaperAngle2.getMinimum()); + ui->taperEdit2->setMaximum(extrude->TaperAngle2.getMaximum()); + ui->taperEdit2->setSingleStep(extrude->TaperAngle2.getStepSize()); + ui->taperEdit2->setValue(taper2); ui->checkBoxAlongDirection->setChecked(alongNormal); ui->checkBoxDirection->setChecked(useCustom); @@ -126,6 +136,8 @@ void TaskExtrudeParameters::setupDialog() ui->lengthEdit->bind(extrude->Length); ui->lengthEdit2->bind(extrude->Length2); ui->offsetEdit->bind(extrude->Offset); + ui->taperEdit->bind(extrude->TaperAngle); + ui->taperEdit2->bind(extrude->TaperAngle2); ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.x"))); ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.y"))); ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.z"))); @@ -172,6 +184,10 @@ void TaskExtrudeParameters::readValuesFromHistory() ui->lengthEdit2->selectNumber(); ui->offsetEdit->setToLastUsedValue(); ui->offsetEdit->selectNumber(); + ui->taperEdit->setToLastUsedValue(); + ui->taperEdit->selectNumber(); + ui->taperEdit2->setToLastUsedValue(); + ui->taperEdit2->selectNumber(); } void TaskExtrudeParameters::connectSlots() @@ -184,6 +200,10 @@ void TaskExtrudeParameters::connectSlots() this, SLOT(onLength2Changed(double))); connect(ui->offsetEdit, SIGNAL(valueChanged(double)), this, SLOT(onOffsetChanged(double))); + connect(ui->taperEdit, SIGNAL(valueChanged(double)), + this, SLOT(onTaperChanged(double))); + connect(ui->taperEdit2, SIGNAL(valueChanged(double)), + this, SLOT(onTaper2Changed(double))); connect(ui->directionCB, SIGNAL(activated(int)), this, SLOT(onDirectionCBChanged(int))); connect(ui->checkBoxAlongDirection, SIGNAL(toggled(bool)), @@ -292,6 +312,20 @@ void TaskExtrudeParameters::onOffsetChanged(double len) tryRecomputeFeature(); } +void TaskExtrudeParameters::onTaperChanged(double angle) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->TaperAngle.setValue(angle); + tryRecomputeFeature(); +} + +void TaskExtrudeParameters::onTaper2Changed(double angle) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->TaperAngle2.setValue(angle); + tryRecomputeFeature(); +} + bool TaskExtrudeParameters::hasProfileFace(PartDesign::ProfileBased* profile) const { try { @@ -769,6 +803,8 @@ void TaskExtrudeParameters::changeEvent(QEvent *e) QSignalBlocker length(ui->lengthEdit); QSignalBlocker length2(ui->lengthEdit2); QSignalBlocker offset(ui->offsetEdit); + QSignalBlocker taper(ui->taperEdit); + QSignalBlocker taper2(ui->taperEdit2); QSignalBlocker xdir(ui->XDirectionEdit); QSignalBlocker ydir(ui->YDirectionEdit); QSignalBlocker zdir(ui->ZDirectionEdit); @@ -804,6 +840,8 @@ void TaskExtrudeParameters::saveHistory(void) ui->lengthEdit->pushToHistory(); ui->lengthEdit2->pushToHistory(); ui->offsetEdit->pushToHistory(); + ui->taperEdit->pushToHistory(); + ui->taperEdit2->pushToHistory(); } void TaskExtrudeParameters::applyParameters(QString facename) @@ -812,6 +850,8 @@ void TaskExtrudeParameters::applyParameters(QString facename) ui->lengthEdit->apply(); ui->lengthEdit2->apply(); + ui->taperEdit->apply(); + ui->taperEdit2->apply(); FCMD_OBJ_CMD(obj, "UseCustomVector = " << (getCustom() ? 1 : 0)); FCMD_OBJ_CMD(obj, "Direction = (" << getXDirection() << ", " << getYDirection() << ", " << getZDirection() << ")"); diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h index 11c58d4328..4b1f49cea6 100644 --- a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h @@ -84,6 +84,8 @@ protected Q_SLOTS: void onLengthChanged(double); void onLength2Changed(double); void onOffsetChanged(double); + void onTaperChanged(double); + void onTaper2Changed(double); void onDirectionCBChanged(int); void onAlongSketchNormalChanged(bool); void onDirectionToggled(bool); @@ -104,13 +106,13 @@ protected: App::PropertyLinkSub* propReferenceAxis; void getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const; + double getOffset(void) const; bool getAlongSketchNormal(void) const; bool getCustom(void) const; std::string getReferenceAxis(void) const; double getXDirection(void) const; double getYDirection(void) const; double getZDirection(void) const; - double getOffset(void) const; bool getReversed(void) const; bool getMidplane(void) const; int getMode(void) const; diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp index ee5b493b60..3f235e350d 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp @@ -55,6 +55,10 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadLength2")); ui->offsetEdit->setEntryName(QByteArray("Offset")); ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadOffset")); + ui->taperEdit->setEntryName(QByteArray("TaperAngle")); + ui->taperEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadTaperAngle")); + ui->taperEdit2->setEntryName(QByteArray("TaperAngle2")); + ui->taperEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadTaperAngle2")); setupDialog(); diff --git a/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui b/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui index ad26a0d45d..e218caa6ea 100644 --- a/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPadPocketParameters.ui @@ -7,7 +7,7 @@ 0 0 300 - 436 + 490 @@ -44,6 +44,9 @@ false + + mm + 0.000000000000000 @@ -61,6 +64,9 @@ false + + mm + @@ -258,6 +264,30 @@ measured along the specified direction + + + + + + Angle to taper the extrusion + + + Taper angle + + + + + + + false + + + deg + + + + + @@ -272,6 +302,33 @@ measured along the specified direction false + + mm + + + + + + + + + + + Angle to taper the extrusion + + + 2nd taper angle + + + + + + + false + + + deg + diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp index 91812bd2f6..83bb539b91 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp @@ -56,6 +56,10 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketLength2")); ui->offsetEdit->setEntryName(QByteArray("Offset")); ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketOffset")); + ui->taperEdit->setEntryName(QByteArray("TaperAngle")); + ui->taperEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketTaperAngle")); + ui->taperEdit2->setEntryName(QByteArray("TaperAngle2")); + ui->taperEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketTaperAngle2")); setupDialog();