Skip to content

Commit

Permalink
Merge pull request FreeCAD#13030 from bgbsww/bgbsww-toponamingFeature…
Browse files Browse the repository at this point in the history
…ExtrusionTwo

Toponaming/Part Move in feature extrusion
  • Loading branch information
chennes authored Mar 22, 2024
2 parents a7416c5 + c31ebee commit 2ebbd83
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 30 deletions.
133 changes: 132 additions & 1 deletion src/Mod/Part/App/ExtrusionHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@

#include <Base/Console.h>
#include <Base/Exception.h>
#include <BRepClass3d_SolidClassifier.hxx>
#include <ShapeFix_Wire.hxx>
// #include <BRepLib_FindSurface.hxx>

#include "ExtrusionHelper.h"
#include "TopoShape.h"
#include "BRepOffsetAPI_MakeOffsetFix.h"

#include "Geometry.h"

using namespace Part;

Expand Down Expand Up @@ -477,3 +481,130 @@ void ExtrusionHelper::createTaperedPrismOffset(TopoDS_Wire sourceWire,
}

}

void ExtrusionHelper::makeElementDraft(const ExtrusionParameters& params,
const TopoShape& _shape,
std::vector<TopoShape>& 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<TopoShape> 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<TopoShape> 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<TopoShape> 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");
}
}
}
22 changes: 22 additions & 0 deletions src/Mod/Part/App/ExtrusionHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<TopoShape>&);
};

} //namespace Part
Expand Down
71 changes: 61 additions & 10 deletions src/Mod/Part/App/FeatureExtrusion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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!
Expand Down Expand Up @@ -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<TopoShape> 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;
}
Expand Down
22 changes: 3 additions & 19 deletions src/Mod/Part/App/FeatureExtrusion.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

#include "FaceMakerCheese.h"
#include "PartFeature.h"

#include "ExtrusionHelper.h"

namespace Part
{
Expand All @@ -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
Expand All @@ -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
Expand Down
27 changes: 27 additions & 0 deletions tests/src/Mod/Part/App/FeatureExtrusion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<Part::Feature*>(_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);
}
2 changes: 2 additions & 0 deletions tests/src/Mod/Part/App/PartTestHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}

Expand Down

0 comments on commit 2ebbd83

Please sign in to comment.