[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.
This commit is contained in:
Uwe
2022-01-07 05:15:28 +01:00
parent 9bb351516a
commit cfdf334b7f
17 changed files with 842 additions and 507 deletions

View File

@@ -425,6 +425,8 @@ SET(Part_SRCS
BSplineCurveBiArcs.cpp
CrossSection.cpp
CrossSection.h
ExtrusionHelper.cpp
ExtrusionHelper.h
GeometryExtension.cpp
GeometryExtension.h
GeometryDefaultExtension.cpp

View File

@@ -0,0 +1,508 @@
/***************************************************************************
* Copyright (c) 2022 Uwe Stöhr <uwestoehr@lyx.org> *
* *
* 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 <cmath>
# include <BRepAlgoAPI_Cut.hxx>
# include <BRepBuilderAPI_MakeWire.hxx>
# include <BRepBuilderAPI_MakeFace.hxx>
# include <BRepBuilderAPI_Sewing.hxx>
# include <BRepBuilderAPI_MakeSolid.hxx>
# include <BRepClass3d_SolidClassifier.hxx>
# include <BRepGProp.hxx>
# include <BRepOffsetAPI_MakeOffset.hxx>
# include <BRepOffsetAPI_ThruSections.hxx>
# include <BRepPrimAPI_MakePrism.hxx>
# include <gp_Dir.hxx>
# include <gp_Trsf.hxx>
# include <GProp_GProps.hxx>
# include <Precision.hxx>
# include <ShapeAnalysis.hxx>
# include <ShapeFix_Wire.hxx>
# include <TopoDS.hxx>
# include <TopoDS_Iterator.hxx>
# include <TopExp.hxx>
# include <TopExp_Explorer.hxx>
#endif
#include "ExtrusionHelper.h"
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Tools.h>
#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<TopoDS_Shape>& drafts,
bool isPartDesign)
{
std::vector<std::vector<TopoDS_Shape>> wiresections;
auto addWiresToWireSections =
[&shape](std::vector<std::vector<TopoDS_Shape>>& 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<TopoDS_Shape>());
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<TopoDS_Wire> 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<std::vector<TopoDS_Shape>> extrusionSections(wiresections.size(), std::vector<TopoDS_Shape>());
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<TopoDS_Shape> 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<bool> isInnerWire(resultPrisms.size(), false);
std::vector<bool> 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<TopoDS_Shape> 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<bool>::iterator isInnerWireIterator = isInnerWire.begin();
std::vector<bool>::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<bool>& isInnerWire, const gp_Dir direction,
std::vector<bool>&checklist, bool forInner, std::vector<TopoDS_Shape> 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<bool>::iterator isInnerWireIterator = isInnerWire.begin();
std::vector<bool>::iterator toCheckIterator = checklist.begin();
// create an array with false used later to store what can be cancelled from the checklist
std::vector<bool> 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");
}
}

View File

@@ -0,0 +1,73 @@
/***************************************************************************
* Copyright (c) 2022 Uwe Stöhr <uwestoehr@lyx.org> *
* *
* 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 <list>
#include <gp_Dir.hxx>
#include <TopoDS_Shape.hxx>
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<TopoDS_Shape>& 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<bool>& isInnerWire, const gp_Dir direction,
std::vector<bool>& checklist, bool forInner, std::vector<TopoDS_Shape> 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

View File

@@ -28,19 +28,12 @@
# include <BRepAdaptor_Curve.hxx>
# include <BRepAlgoAPI_Cut.hxx>
# include <BRepBuilderAPI_Copy.hxx>
# include <BRepBuilderAPI_MakeFace.hxx>
# include <BRepBuilderAPI_MakeSolid.hxx>
# include <BRepBuilderAPI_MakeWire.hxx>
# include <BRepBuilderAPI_Sewing.hxx>
# include <BRepClass3d_SolidClassifier.hxx>
# include <BRepGProp.hxx>
# include <BRepLib_FindSurface.hxx>
# include <BRepOffsetAPI_MakeOffset.hxx>
# include <BRepOffsetAPI_ThruSections.hxx>
# include <BRepPrimAPI_MakePrism.hxx>
# include <gp_Pln.hxx>
# include <gp_Trsf.hxx>
# include <GProp_GProps.hxx>
# include <Precision.hxx>
# include <ShapeAnalysis.hxx>
# include <ShapeFix_Wire.hxx>
@@ -55,6 +48,7 @@
#include <App/Application.h>
#include <Base/Exception.h>
#include <Base/Tools.h>
#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<TopoDS_Shape> 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<TopoDS_Shape>& drafts)
{
std::vector<std::vector<TopoDS_Shape>> wiresections;
auto addWiresToWireSections =
[&shape](std::vector<std::vector<TopoDS_Shape>>& 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<TopoDS_Shape>());
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<TopoDS_Wire> 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<std::vector<TopoDS_Shape>> extrusionSections(wiresections.size(), std::vector<TopoDS_Shape>());
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<TopoDS_Shape> 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<bool> isInnerWire(resultPrisms.size(), false);
std::vector<bool> 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<TopoDS_Shape> 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<bool>::iterator isInnerWireIterator = isInnerWire.begin();
std::vector<bool>::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<bool>& isInnerWire, const gp_Dir direction,
std::vector<bool>& checklist, bool forInner, std::vector<TopoDS_Shape> 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<bool>::iterator isInnerWireIterator = isInnerWire.begin();
std::vector<bool>::iterator toCheckIterator = checklist.begin();
// create an array with false used later to store what can be cancelled from the checklist
std::vector<bool> 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)

View File

@@ -120,29 +120,6 @@ public: //mode enumerations
};
static const char* eDirModeStrings[];
protected:
static void makeDraft(const ExtrusionParameters& params, const TopoDS_Shape&, std::list<TopoDS_Shape>&);
/**
* @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<bool>& isInnerWire, const gp_Dir direction,
std::vector<bool>& checklist, bool forInner, std::vector<TopoDS_Shape> 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;
};

View File

@@ -51,15 +51,17 @@
#include <Base/Exception.h>
#include <Base/Placement.h>
#include <Base/Reader.h>
#include <Base/Tools.h>
#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<double>(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() ||

View File

@@ -24,8 +24,10 @@
#ifndef PARTDESIGN_FEATURE_EXTRUDE_H
#define PARTDESIGN_FEATURE_EXTRUDE_H
#include <App/PropertyGeo.h>
#include <App/PropertyStandard.h>
#include <App/PropertyUnits.h>
#include <Base/Tools.h>
#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 */
//@{

View File

@@ -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())

View File

@@ -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");

View File

@@ -35,6 +35,7 @@
# include <BRepBuilderAPI_Copy.hxx>
# include <BRepBuilderAPI_MakeEdge.hxx>
# include <BRepBuilderAPI_MakeFace.hxx>
# include <BRepBuilderAPI_MakeWire.hxx>
# include <BRepCheck_Analyzer.hxx>
# include <BRepExtrema_DistShapeShape.hxx>
# include <BRepGProp.hxx>
@@ -42,6 +43,8 @@
# include <BRepLProp_SLProps.hxx>
# include <BRepPrimAPI_MakePrism.hxx>
# include <BRepFeat_MakePrism.hxx>
# include <BRepOffsetAPI_MakeOffset.hxx>
# include <BRepOffsetAPI_ThruSections.hxx>
# include <BRepProj_Projection.hxx>
# include <Extrema_ExtCC.hxx>
# include <Extrema_POnCurv.hxx>
@@ -73,9 +76,11 @@
#include <Base/Parameter.h>
#include <Base/Reader.h>
#include <Base/Console.h>
#include <Base/Tools.h>
#include <App/Application.h>
#include <App/OriginFeature.h>
#include <App/Document.h>
#include <Mod/Part/App/ExtrusionHelper.h>
#include <Mod/Part/App/FaceMakerCheese.h>
#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<TopoDS_Shape> 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<TopoDS_Shape>::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 {

View File

@@ -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,

View File

@@ -82,6 +82,7 @@
# include <BRepLProp_SLProps.hxx>
# include <BRepProj_Projection.hxx>
# include <BRepBuilderAPI_MakeSolid.hxx>
# include <BRepBuilderAPI_MakeWire.hxx>
# include <BRepBuilderAPI_Sewing.hxx>
# include <BRepBuilderAPI_MakePolygon.hxx>
# include <BRepBuilderAPI_MakeFace.hxx>
@@ -89,6 +90,7 @@
# include <BRepExtrema_DistShapeShape.hxx>
# include <BRepFilletAPI_MakeChamfer.hxx>
# include <BRepOffsetAPI_DraftAngle.hxx>
# include <BRepOffsetAPI_MakeOffset.hxx>
# include <BRepOffsetAPI_ThruSections.hxx>
# include <BRepPrimAPI_MakeBox.hxx>
# include <BRepPrimAPI_MakeCylinder.hxx>

View File

@@ -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<PartDesign::FeatureExtrude*>(vp->getObject());
extrude->TaperAngle.setValue(angle);
tryRecomputeFeature();
}
void TaskExtrudeParameters::onTaper2Changed(double angle)
{
PartDesign::FeatureExtrude* extrude = static_cast<PartDesign::FeatureExtrude*>(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() << ")");

View File

@@ -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<std::string>& 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;

View File

@@ -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();

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>300</width>
<height>436</height>
<height>490</height>
</rect>
</property>
<property name="windowTitle">
@@ -44,6 +44,9 @@
<property name="keyboardTracking">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
@@ -61,6 +64,9 @@
<property name="keyboardTracking">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
</widget>
</item>
</layout>
@@ -258,6 +264,30 @@ measured along the specified direction</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="labelTaperAngle">
<property name="toolTip">
<string>Angle to taper the extrusion</string>
</property>
<property name="text">
<string>Taper angle</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefQuantitySpinBox" name="taperEdit">
<property name="keyboardTracking">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
@@ -272,6 +302,33 @@ measured along the specified direction</string>
<property name="keyboardTracking">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="labelTaperAngle2">
<property name="toolTip">
<string>Angle to taper the extrusion</string>
</property>
<property name="text">
<string>2nd taper angle</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefQuantitySpinBox" name="taperEdit2">
<property name="keyboardTracking">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
</widget>
</item>
</layout>

View File

@@ -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();