diff --git a/src/Mod/Part/App/ExtrusionHelper.cpp b/src/Mod/Part/App/ExtrusionHelper.cpp
index c6128cce1be3..191d260dc523 100644
--- a/src/Mod/Part/App/ExtrusionHelper.cpp
+++ b/src/Mod/Part/App/ExtrusionHelper.cpp
@@ -41,10 +41,14 @@
#include
#include
+#include
+#include
+// #include
#include "ExtrusionHelper.h"
+#include "TopoShape.h"
#include "BRepOffsetAPI_MakeOffsetFix.h"
-
+#include "Geometry.h"
using namespace Part;
@@ -477,3 +481,130 @@ void ExtrusionHelper::createTaperedPrismOffset(TopoDS_Wire sourceWire,
}
}
+
+void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params,
+ const TopoShape& _shape,
+ std::vector& drafts)
+{
+ 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();
+ bool bMid = !bFwd || !bRev
+ || params.lengthFwd * params.lengthRev > 0.0; // include the source shape as loft section?
+
+ TopoShape shape = _shape;
+ TopoShape sourceWire;
+ if (shape.isNull()) {
+ Standard_Failure::Raise("Not a valid shape");
+ }
+
+ if (params.solid && !shape.hasSubShape(TopAbs_FACE)) {
+ shape = shape.makeElementFace(nullptr, params.faceMakerClass.c_str());
+ }
+
+ if (shape.shapeType() == TopAbs_FACE) {
+ std::vector wires;
+ TopoShape outerWire = shape.splitWires(&wires, TopoShape::ReorientForward);
+ if (outerWire.isNull()) {
+ Standard_Failure::Raise("Missing outer wire");
+ }
+ if (wires.empty()) {
+ shape = outerWire;
+ }
+ else {
+ unsigned pos = drafts.size();
+ makeElementDraft(params, outerWire, drafts);
+ if (drafts.size() != pos + 1) {
+ Standard_Failure::Raise("Failed to make drafted extrusion");
+ }
+ std::vector inner;
+ TopoShape innerWires(0);
+ innerWires.makeElementCompound(
+ wires,
+ "",
+ TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
+ makeElementDraft(params, innerWires, inner);
+ if (inner.empty()) {
+ Standard_Failure::Raise("Failed to make drafted extrusion with inner hole");
+ }
+ inner.insert(inner.begin(), drafts.back());
+ drafts.back().makeElementCut(inner);
+ return;
+ }
+ }
+
+ if (shape.shapeType() == TopAbs_WIRE) {
+ ShapeFix_Wire aFix;
+ aFix.Load(TopoDS::Wire(shape.getShape()));
+ aFix.FixReorder();
+ aFix.FixConnected();
+ aFix.FixClosed();
+ sourceWire.setShape(aFix.Wire());
+ sourceWire.Tag = shape.Tag;
+ sourceWire.mapSubElement(shape);
+ }
+ else if (shape.shapeType() == TopAbs_COMPOUND) {
+ for (auto& s : shape.getSubTopoShapes()) {
+ makeElementDraft(params, s, drafts);
+ }
+ }
+ else {
+ Standard_Failure::Raise("Only a wire or a face is supported");
+ }
+
+ if (!sourceWire.isNull()) {
+ std::vector list_of_sections;
+ if (bRev) {
+ TopoDS_Wire offsetWire;
+ createTaperedPrismOffset(TopoDS::Wire(sourceWire.getShape()),
+ vecRev,
+ distanceRev,
+ false,
+ offsetWire);
+ list_of_sections.push_back(TopoShape(offsetWire, sourceWire.Tag));
+ }
+
+ // next. Add source wire as middle section. Order is important.
+ if (bMid) {
+ list_of_sections.push_back(sourceWire);
+ }
+
+ // finally. Forward extrusion offset wire.
+ if (bFwd) {
+ TopoDS_Wire offsetWire;
+ createTaperedPrismOffset(TopoDS::Wire(sourceWire.getShape()),
+ vecFwd,
+ distanceFwd,
+ false,
+ offsetWire);
+ list_of_sections.push_back(TopoShape(offsetWire, sourceWire.Tag));
+ }
+
+ try {
+#if defined(__GNUC__) && defined(FC_OS_LINUX)
+ Base::SignalException se;
+#endif
+
+ // make loft
+ BRepOffsetAPI_ThruSections mkGenerator(params.solid ? Standard_True : Standard_False,
+ /*ruled=*/Standard_True);
+ for (auto& s : list_of_sections) {
+ mkGenerator.AddWire(TopoDS::Wire(s.getShape()));
+ }
+
+ mkGenerator.Build();
+ drafts.push_back(TopoShape(0).makeElementShape(mkGenerator, list_of_sections));
+ }
+ catch (Standard_Failure&) {
+ throw;
+ }
+ catch (...) {
+ throw Base::CADKernelError("Unknown exception from BRepOffsetAPI_ThruSections");
+ }
+ }
+}
diff --git a/src/Mod/Part/App/ExtrusionHelper.h b/src/Mod/Part/App/ExtrusionHelper.h
index 90c24eab594e..bff010a94d93 100644
--- a/src/Mod/Part/App/ExtrusionHelper.h
+++ b/src/Mod/Part/App/ExtrusionHelper.h
@@ -34,6 +34,24 @@
namespace Part
{
+class TopoShape;
+
+/**
+ * @brief The ExtrusionParameters struct is supposed to be filled with final
+ * extrusion parameters, after resolving links, applying mode logic,
+ * reversing, etc., and be passed to extrudeShape.
+ */
+struct ExtrusionParameters
+{
+ gp_Dir dir;
+ double lengthFwd {0};
+ double lengthRev {0};
+ bool solid {false};
+ double taperAngleFwd {0}; // in radians
+ double taperAngleRev {0};
+ std::string faceMakerClass;
+};
+
class PartExport ExtrusionHelper
{
public:
@@ -68,6 +86,10 @@ class PartExport ExtrusionHelper
double offset,
bool isSecond,
TopoDS_Wire& result);
+ /** Same as makeDraft() with support of element mapping
+ */
+ static void
+ makeElementDraft(const ExtrusionParameters& params, const TopoShape&, std::vector&);
};
} //namespace Part
diff --git a/src/Mod/Part/App/FeatureExtrusion.cpp b/src/Mod/Part/App/FeatureExtrusion.cpp
index 53b72a5bf6bf..ae12b30f2c85 100644
--- a/src/Mod/Part/App/FeatureExtrusion.cpp
+++ b/src/Mod/Part/App/FeatureExtrusion.cpp
@@ -127,9 +127,9 @@ bool Extrusion::fetchAxisLink(const App::PropertyLinkSub& axisLink, Base::Vector
return true;
}
-Extrusion::ExtrusionParameters Extrusion::computeFinalParameters()
+ExtrusionParameters Extrusion::computeFinalParameters()
{
- Extrusion::ExtrusionParameters result;
+ ExtrusionParameters result;
Base::Vector3d dir;
switch (this->DirMode.getValue()) {
case dmCustom:
@@ -229,11 +229,10 @@ Base::Vector3d Extrusion::calculateShapeNormal(const App::PropertyLink& shapeLin
return Base::Vector3d(normal.X(), normal.Y(), normal.Z());
}
-TopoShape Extrusion::extrudeShape(const TopoShape& source, const Extrusion::ExtrusionParameters& params)
+void Extrusion::extrudeShape(TopoShape &result, const TopoShape &source, const ExtrusionParameters& params)
{
- TopoDS_Shape result;
gp_Vec vec = gp_Vec(params.dir).Multiplied(params.lengthFwd + params.lengthRev);//total vector of extrusion
-
+#ifndef FC_USE_TNP_FIX
if (std::fabs(params.taperAngleFwd) >= Precision::Angular() ||
std::fabs(params.taperAngleRev) >= Precision::Angular()) {
//Tapered extrusion!
@@ -306,20 +305,72 @@ TopoShape Extrusion::extrudeShape(const TopoShape& source, const Extrusion::Extr
result = mkPrism.Shape();
}
- if (result.IsNull())
+ if (result.isNull())
throw NullShapeException("Result of extrusion is null shape.");
- return TopoShape(result);
+// return TopoShape(result);
+#else
+
+ // #0000910: Circles Extrude Only Surfaces, thus use BRepBuilderAPI_Copy
+ TopoShape myShape(source.makeElementCopy());
+
+ if (std::fabs(params.taperAngleFwd) >= Precision::Angular()
+ || std::fabs(params.taperAngleRev) >= Precision::Angular()) {
+ // Tapered extrusion!
+#if defined(__GNUC__) && defined(FC_OS_LINUX)
+ Base::SignalException se;
+#endif
+ std::vector drafts;
+ ExtrusionHelper::makeElementDraft(params, myShape, drafts);
+ if (drafts.empty()) {
+ Standard_Failure::Raise("Drafting shape failed");
+ }
+ else {
+ result.makeElementCompound(drafts,
+ 0,
+ TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
+ }
+ }
+ else {
+ // Regular (non-tapered) extrusion!
+ if (source.isNull()) {
+ Standard_Failure::Raise("Cannot extrude empty shape");
+ }
+
+ // apply reverse part of extrusion by shifting the source shape
+ if (fabs(params.lengthRev) > Precision::Confusion()) {
+ gp_Trsf mov;
+ mov.SetTranslation(gp_Vec(params.dir) * (-params.lengthRev));
+ myShape = myShape.makeElementTransform(mov);
+ }
+
+ // make faces from wires
+ if (params.solid) {
+ // test if we need to make faces from wires. If there are faces - we don't.
+ if (!myShape.hasSubShape(TopAbs_FACE)) {
+ if (!myShape.Hasher) {
+ myShape.Hasher = result.Hasher;
+ }
+ myShape = myShape.makeElementFace(nullptr, params.faceMakerClass.c_str());
+ }
+ }
+
+ // extrude!
+ result.makeElementPrism(myShape, vec);
+ }
+#endif
}
App::DocumentObjectExecReturn* Extrusion::execute()
{
App::DocumentObject* link = Base.getValue();
- if (!link)
+ if (!link) {
return new App::DocumentObjectExecReturn("No object linked");
+ }
try {
- Extrusion::ExtrusionParameters params = computeFinalParameters();
- TopoShape result = extrudeShape(Feature::getShape(link), params);
+ ExtrusionParameters params = computeFinalParameters();
+ TopoShape result(0);
+ extrudeShape(result, Feature::getTopoShape(link), params);
this->Shape.setValue(result);
return App::DocumentObject::StdReturn;
}
diff --git a/src/Mod/Part/App/FeatureExtrusion.h b/src/Mod/Part/App/FeatureExtrusion.h
index 0101972b6065..eb1c976f7adb 100644
--- a/src/Mod/Part/App/FeatureExtrusion.h
+++ b/src/Mod/Part/App/FeatureExtrusion.h
@@ -28,7 +28,7 @@
#include "FaceMakerCheese.h"
#include "PartFeature.h"
-
+#include "ExtrusionHelper.h"
namespace Part
{
@@ -53,22 +53,6 @@ class PartExport Extrusion : public Part::Feature
App::PropertyAngle TaperAngleRev;
App::PropertyString FaceMakerClass;
-
- /**
- * @brief The ExtrusionParameters struct is supposed to be filled with final
- * extrusion parameters, after resolving links, applying mode logic,
- * reversing, etc., and be passed to extrudeShape.
- */
- struct ExtrusionParameters {
- gp_Dir dir;
- double lengthFwd{0};
- double lengthRev{0};
- bool solid{false};
- double taperAngleFwd{0}; //in radians
- double taperAngleRev{0};
- std::string faceMakerClass;
- };
-
/** @name methods override feature */
//@{
/// recalculate the feature
@@ -82,11 +66,11 @@ class PartExport Extrusion : public Part::Feature
/**
* @brief extrudeShape powers the extrusion feature.
+ * @param result: result of extrusion
* @param source: the shape to be extruded
* @param params: extrusion parameters
- * @return result of extrusion
*/
- static TopoShape extrudeShape(const TopoShape& source, const ExtrusionParameters& params);
+ static void extrudeShape(TopoShape &result, const TopoShape &source, const ExtrusionParameters& params);
/**
* @brief fetchAxisLink: read AxisLink to obtain the direction and
diff --git a/tests/src/Mod/Part/App/FeatureExtrusion.cpp b/tests/src/Mod/Part/App/FeatureExtrusion.cpp
index 92fcb4909d4d..7374ed729b8b 100644
--- a/tests/src/Mod/Part/App/FeatureExtrusion.cpp
+++ b/tests/src/Mod/Part/App/FeatureExtrusion.cpp
@@ -7,6 +7,7 @@
#include "BRepBuilderAPI_MakeEdge.hxx"
#include "PartTestHelpers.h"
+#include "Mod/Sketcher/App/SketchObject.h"
class FeatureExtrusionTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass
{
@@ -298,3 +299,29 @@ TEST_F(FeatureExtrusionTest, testExecuteFaceMaker)
EXPECT_FLOAT_EQ(volume, len * wid * ext1);
EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, 0, len, wid, ext1)));
}
+
+TEST_F(FeatureExtrusionTest, testFaceWithHoles)
+{
+ // Arrange
+ float radius = 0.75;
+ auto [face1, wire1, wire2] = PartTestHelpers::CreateFaceWithRoundHole(len, wid, radius);
+ // face1 is the sum of the outside (wire1) and the internal hole (wire2).
+ Part::TopoShape newFace = Part::TopoShape(face1).makeElementFace(nullptr);
+ // newFace cleans that up and is the outside minus the internal hole.
+ auto face2 = newFace.getShape();
+
+ auto partFeature = dynamic_cast(_doc->addObject("Part::Feature"));
+ partFeature->Shape.setValue(face2);
+ _extrusion->Base.setValue(_doc->getObjects().back());
+ _extrusion->FaceMakerClass.setValue("Part::FaceMakerCheese");
+ // Act
+ _extrusion->execute();
+ Part::TopoShape ts = _extrusion->Shape.getValue();
+ double volume = PartTestHelpers::getVolume(ts.getShape());
+ Base::BoundBox3d bb = ts.getBoundBox();
+ // Assert
+ EXPECT_FLOAT_EQ(volume, len * wid * ext1 - radius * radius * M_PI * ext1);
+ EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, 0, len, wid, ext1)));
+ EXPECT_FLOAT_EQ(PartTestHelpers::getArea(face1), len * wid + radius * radius * M_PI);
+ EXPECT_FLOAT_EQ(PartTestHelpers::getArea(face2), len * wid - radius * radius * M_PI);
+}
diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp
index a837d16207a7..59e5ac39f71c 100644
--- a/tests/src/Mod/Part/App/PartTestHelpers.cpp
+++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp
@@ -111,6 +111,8 @@ CreateFaceWithRoundHole(float len, float wid, float radius)
auto edge5 = BRepBuilderAPI_MakeEdge(circ1).Edge();
auto wire2 = BRepBuilderAPI_MakeWire(edge5).Wire();
auto face2 = BRepBuilderAPI_MakeFace(face1, wire2).Face();
+ // Beware: somewhat counterintuitively, face2 is the sum of face1 and the area inside wire2,
+ // not the difference.
return {face2, wire1, wire2};
}