PD: Improve groove feature

The only difference between groove and revolution is that for the former
the revolved face is removed from the base shape instead of added.

Thus, the code of the Revolution and Groove classes should be almost
identical. This allows it in a further step to refactor the code and
make a common base class.

This fixes issue 18842.
This commit is contained in:
wmayer
2025-02-17 12:27:20 +01:00
committed by Ladislav Michl
parent 4d80f3ec28
commit df5c75d6b0
2 changed files with 188 additions and 152 deletions

View File

@@ -25,10 +25,9 @@
# include <BRepPrimAPI_MakeRevol.hxx>
# include <BRepFeat_MakeRevol.hxx>
# include <gp_Lin.hxx>
# include <TopoDS.hxx>
# include <TopExp_Explorer.hxx>
# include <Precision.hxx>
# include <TopExp_Explorer.hxx>
# include <TopoDS.hxx>
#include <Base/Exception.h>
#include <Base/Tools.h>
@@ -36,7 +35,6 @@
#include "FeatureGroove.h"
#include "Mod/Part/App/TopoShapeOpCode.h"
using namespace PartDesign;
namespace PartDesign {
@@ -47,7 +45,7 @@ const char* Groove::TypeEnums[]= {"Angle", "ThroughAll", "UpToFirst", "UpToFace"
PROPERTY_SOURCE(PartDesign::Groove, PartDesign::ProfileBased)
const App::PropertyAngle::Constraints Groove::floatAngle = { Base::toDegrees<double>(Precision::Angular()), 360.0, 1.0 };
const App::PropertyAngle::Constraints Groove::floatAngle = { 0.0, 360.0, 1.0 };
Groove::Groove()
{
@@ -58,10 +56,10 @@ Groove::Groove()
ADD_PROPERTY_TYPE(Base, (Base::Vector3d(0.0f,0.0f,0.0f)), "Groove", App::PropertyType(App::Prop_ReadOnly | App::Prop_Hidden), "Base");
ADD_PROPERTY_TYPE(Axis, (Base::Vector3d(0.0f,1.0f,0.0f)), "Groove", App::PropertyType(App::Prop_ReadOnly | App::Prop_Hidden), "Axis");
ADD_PROPERTY_TYPE(Angle, (360.0),"Groove", App::Prop_None, "Angle");
ADD_PROPERTY_TYPE(Angle2, (60.0), "Groove", App::Prop_None, "Groove length in 2nd direction");
ADD_PROPERTY_TYPE(Angle2, (0.0), "Groove", App::Prop_None, "Groove length in 2nd direction");
ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Groove", App::Prop_None, "Face where groove will end");
Angle.setConstraints(&floatAngle);
ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Groove", (App::PropertyType)(App::Prop_None), "Reference axis of Groove");
ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Groove", (App::Prop_None), "Reference axis of groove");
}
short Groove::mustExecute() const
@@ -81,57 +79,63 @@ App::DocumentObjectExecReturn *Groove::execute()
{
if (onlyHaveRefined()) { return App::DocumentObject::StdReturn; }
constexpr double maxDegree = 360.0;
auto method = methodFromString(Type.getValueAsString());
// Validate parameters
double angle = Angle.getValue();
if (angle > 360.0)
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too large"));
angle = Base::toRadians<double>(angle);
if (angle < Precision::Angular())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too small"));
// Reverse angle if selected
if (Reversed.getValue() && !Midplane.getValue())
angle *= (-1.0);
TopoShape sketchshape;
try {
sketchshape = getTopoShapeVerifiedFace();
} catch (const Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
double angleDeg = Angle.getValue();
if (angleDeg > maxDegree) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Angle of groove too large"));
}
// if the Base property has a valid shape, fuse the prism into it
double angle = Base::toRadians<double>(angleDeg);
if (angle < Precision::Angular() && method == RevolMethod::Angle) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Angle of groove too small"));
}
double angle2 = Base::toRadians(Angle2.getValue());
if (std::fabs(angle + angle2) < Precision::Angular() && method == RevolMethod::TwoAngles) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Angles of groove nullify each other"));
}
TopoShape sketchshape = getTopoShapeVerifiedFace();
// if the Base property has a valid shape, fuse the AddShape into it
TopoShape base;
try {
base = getBaseTopoShape();
}
catch (const Base::Exception&) {
std::string text(QT_TRANSLATE_NOOP("Exception", "The requested feature cannot be created. The reason may be that:\n"
" - the active Body does not contain a base shape, so there is no\n"
" material to be removed;\n"
" - the selected sketch does not belong to the active Body."));
return new App::DocumentObjectExecReturn(text);
// fall back to support (for legacy features)
}
updateAxis();
// get revolve axis
Base::Vector3d b = Base.getValue();
gp_Pnt pnt(b.x,b.y,b.z);
Base::Vector3d v = Axis.getValue();
gp_Dir dir(v.x,v.y,v.z);
// update Axis from ReferenceAxis
try {
updateAxis();
}
catch (const Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
}
try {
if (sketchshape.isNull())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
// get revolve axis
Base::Vector3d b = Base.getValue();
gp_Pnt pnt(b.x, b.y, b.z);
Base::Vector3d v = Axis.getValue();
// Rotate the face by half the angle to get Groove symmetric to sketch plane
if (Midplane.getValue()) {
gp_Trsf mov;
mov.SetRotation(gp_Ax1(pnt, dir), Base::toRadians<double>(Angle.getValue()) * (-1.0) / 2.0);
TopLoc_Location loc(mov);
sketchshape.move(loc);
if (v.IsNull()) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Reference axis is invalid"));
}
gp_Dir dir(v.x, v.y, v.z);
if (sketchshape.isNull()) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
}
this->positionByPrevious();
@@ -144,66 +148,112 @@ App::DocumentObjectExecReturn *Groove::execute()
// Check distance between sketchshape and axis - to avoid failures and crashes
TopExp_Explorer xp;
xp.Init(sketchshape.getShape(), TopAbs_FACE);
for (;xp.More(); xp.Next()) {
if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current())))
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch"));
}
// revolve the face to a solid
TopoShape result(0);
try {
result.makeElementRevolve(sketchshape, gp_Ax1(pnt, dir), angle);
}catch(Standard_Failure &) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!"));
}
this->AddSubShape.setValue(result);
if(base.isNull()) {
Shape.setValue(getSolid(result));
return App::DocumentObject::StdReturn;
}
result.Tag = -getID();
TopoShape boolOp(0);
try {
const char *maker;
switch (getAddSubType()) {
case Additive:
maker = Part::OpCodes::Fuse;
break;
// case Intersecting:
// maker = Part::OpCodes::Common;
// break;
default:
maker = Part::OpCodes::Cut;
for (; xp.More(); xp.Next()) {
if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch"));
}
// this->fixShape(result);
boolOp.makeElementBoolean(maker, {base,result});
}catch(Standard_Failure &) {
return new App::DocumentObjectExecReturn("Failed to cut base feature");
}
TopoShape solid = this->getSolid(boolOp);
if (solid.isNull())
return new App::DocumentObjectExecReturn("Resulting shape is not a solid");
// store shape before refinement
this->rawShape = boolOp;
boolOp = refineShapeIfActive(boolOp);
if (!isSingleSolidRuleSatisfied(boolOp.getShape())) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: enable 'Allow Compound' in the active body."));
// Create a fresh support even when base exists so that it can be used for patterns
TopoShape result(0);
TopoShape supportface(0);
try {
supportface = getSupportFace();
}
boolOp = getSolid(boolOp);
Shape.setValue(boolOp);
catch(...) {
// do nothing, null shape is handled below
}
supportface.move(invObjLoc);
if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst) {
TopoShape upToFace;
if (method == RevolMethod::ToFace) {
getUpToFaceFromLinkSub(upToFace, UpToFace);
upToFace.move(invObjLoc);
}
else {
throw Base::RuntimeError(
"ProfileBased: Revolution up to first/last is not yet supported");
}
if (Reversed.getValue()) {
dir.Reverse();
}
TopExp_Explorer Ex(supportface.getShape(), TopAbs_WIRE);
if (!Ex.More()) {
supportface = TopoDS_Face();
}
try {
result = base.makeElementRevolution(base,
TopoDS::Face(sketchshape.getShape()),
gp_Ax1(pnt, dir),
TopoDS::Face(supportface.getShape()),
TopoDS::Face(upToFace.getShape()),
nullptr,
Part::RevolMode::CutFromBase,
Standard_True);
}
catch (Standard_Failure&) {
return new App::DocumentObjectExecReturn("Could not revolve the sketch!");
}
}
else {
bool midplane = Midplane.getValue();
bool reversed = Reversed.getValue();
generateRevolution(result,
sketchshape.getShape(),
gp_Ax1(pnt, dir),
angle,
angle2,
midplane,
reversed,
method);
}
if (!result.isNull()) {
// store shape before refinement
this->rawShape = result;
result = refineShapeIfActive(result);
// set the additive shape property for later usage in e.g. pattern
this->AddSubShape.setValue(result);
if (!base.isNull()) {
result = base.makeElementCut(result);
// store shape before refinement
this->rawShape = result;
result = refineShapeIfActive(result);
}
if (!isSingleSolidRuleSatisfied(result.getShape())) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: enable 'Allow Compound' in the active body."));
}
result = getSolid(result);
this->Shape.setValue(result);
}
else {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!"));
}
// eventually disable some settings that are not valid for the current method
updateProperties(method);
return App::DocumentObject::StdReturn;
}
catch (Standard_Failure& e) {
if (std::string(e.GetMessageString()) == "TopoDS::Face")
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not create face from sketch.\n"
"Intersecting sketch entities in a sketch are not allowed."));
else
if (std::string(e.GetMessageString()) == "TopoDS::Face") {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception",
"Could not create face from sketch.\n"
"Intersecting sketch entities in a sketch are not allowed."));
}
else {
return new App::DocumentObjectExecReturn(e.GetMessageString());
}
}
catch (Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
@@ -212,7 +262,12 @@ App::DocumentObjectExecReturn *Groove::execute()
bool Groove::suggestReversed()
{
updateAxis();
try {
updateAxis();
} catch (const Base::Exception&) {
return false;
}
return ProfileBased::getReversedAngle(Base.getValue(), Axis.getValue()) > 0.0;
}
@@ -224,16 +279,14 @@ void Groove::updateAxis()
Base::Vector3d dir;
getAxis(pcReferenceAxis, subReferenceAxis, base, dir, ForbiddenAxis::NotParallelWithNormal);
if (dir.Length() > Precision::Confusion()) {
Base.setValue(base.x,base.y,base.z);
Axis.setValue(dir.x,dir.y,dir.z);
}
Base.setValue(base.x,base.y,base.z);
Axis.setValue(dir.x,dir.y,dir.z);
}
Groove::RevolMethod Groove::methodFromString(const std::string& methodStr)
{
if (methodStr == "Angle")
return RevolMethod::Dimension;
return RevolMethod::Angle;
if (methodStr == "UpToLast")
return RevolMethod::ToLast;
if (methodStr == "ThroughAll")
@@ -243,14 +296,13 @@ Groove::RevolMethod Groove::methodFromString(const std::string& methodStr)
if (methodStr == "UpToFace")
return RevolMethod::ToFace;
if (methodStr == "TwoAngles")
return RevolMethod::TwoDimensions;
return RevolMethod::TwoAngles;
throw Base::ValueError("Groove:: No such method");
return RevolMethod::Dimension;
}
void Groove::generateRevolution(TopoDS_Shape& revol,
const TopoDS_Shape& sketchshape,
void Groove::generateRevolution(TopoShape& revol,
const TopoShape& sketchshape,
const gp_Ax1& axis,
const double angle,
const double angle2,
@@ -258,11 +310,11 @@ void Groove::generateRevolution(TopoDS_Shape& revol,
const bool reversed,
RevolMethod method)
{
if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) {
if (method == RevolMethod::Angle || method == RevolMethod::TwoAngles || method == RevolMethod::ThroughAll) {
double angleTotal = angle;
double angleOffset = 0.;
if (method == RevolMethod::TwoDimensions) {
if (method == RevolMethod::TwoAngles) {
// Rotate the face by `angle2`/`angle` to get "second" angle
angleTotal += angle2;
angleOffset = angle2 * -1.0;
@@ -271,43 +323,41 @@ void Groove::generateRevolution(TopoDS_Shape& revol,
angleTotal = 2 * std::numbers::pi;
}
else if (midplane) {
// Rotate the face by half the angle to get Groove symmetric to sketch plane
// Rotate the face by half the angle to get Revolution symmetric to sketch plane
angleOffset = -angle / 2;
}
if (fabs(angleTotal) < Precision::Angular())
throw Base::ValueError("Cannot create a revolution with zero angle.");
TopoDS_Shape from = sketchshape;
if (method == RevolMethod::TwoDimensions || midplane) {
gp_Trsf mov;
mov.SetRotation(axis, angleOffset);
TopLoc_Location loc(mov);
from.Move(loc);
gp_Ax1 revolAx(axis);
if (reversed) {
revolAx.Reverse();
}
else if (reversed) {
angleTotal *= -1.0;
TopoShape from = sketchshape;
if (method == RevolMethod::TwoAngles || midplane) {
gp_Trsf mov;
mov.SetRotation(revolAx, angleOffset);
TopLoc_Location loc(mov);
from.move(loc);
}
// revolve the face to a solid
// BRepPrimAPI is the only option that allows use of this shape for patterns.
// See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673.
BRepPrimAPI_MakeRevol RevolMaker(from, axis, angleTotal);
if (!RevolMaker.IsDone())
throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!");
else
revol = RevolMaker.Shape();
}
else {
revol = from;
revol = revol.makeElementRevolve(revolAx,angleTotal);
revol.Tag = -getID();
} else {
std::stringstream str;
str << "ProfileBased: Internal error: Unknown method for generateGroove()";
str << "ProfileBased: Internal error: Unknown method for generateRevolution()";
throw Base::RuntimeError(str.str());
}
}
void Groove::generateRevolution(TopoDS_Shape& revol,
const TopoDS_Shape& baseshape,
void Groove::generateRevolution(TopoShape& revol,
const TopoShape& baseshape,
const TopoDS_Shape& profileshape,
const TopoDS_Face& supportface,
const TopoDS_Face& uptoface,
@@ -317,20 +367,8 @@ void Groove::generateRevolution(TopoDS_Shape& revol,
Standard_Boolean Modify)
{
if (method == RevolMethod::ToFirst || method == RevolMethod::ToFace || method == RevolMethod::ToLast) {
BRepFeat_MakeRevol RevolMaker;
TopoDS_Shape base = baseshape;
for (TopExp_Explorer xp(profileshape, TopAbs_FACE); xp.More(); xp.Next()) {
RevolMaker.Init(base, xp.Current(), supportface, axis, Mode, Modify);
RevolMaker.Perform(uptoface);
if (!RevolMaker.IsDone())
throw Base::RuntimeError("ProfileBased: Up to face: Could not revolve the sketch!");
base = RevolMaker.Shape();
if (Mode == RevolMode::None)
Mode = RevolMode::FuseWithBase;
}
revol = base;
revol = revol.makeElementRevolution(baseshape, profileshape, axis, supportface, uptoface, nullptr,
static_cast<Part::RevolMode>(Mode), Modify, nullptr);
}
else {
std::stringstream str;
@@ -348,7 +386,7 @@ void Groove::updateProperties(RevolMethod method)
bool isMidplaneEnabled = false;
bool isReversedEnabled = false;
bool isUpToFaceEnabled = false;
if (method == RevolMethod::Dimension) {
if (method == RevolMethod::Angle) {
isAngleEnabled = true;
isMidplaneEnabled = true;
isReversedEnabled = !Midplane.getValue();
@@ -367,7 +405,7 @@ void Groove::updateProperties(RevolMethod method)
isReversedEnabled = true;
isUpToFaceEnabled = true;
}
else if (method == RevolMethod::TwoDimensions) {
else if (method == RevolMethod::TwoAngles) {
isAngleEnabled = true;
isAngle2Enabled = true;
isReversedEnabled = true;
@@ -380,7 +418,5 @@ void Groove::updateProperties(RevolMethod method)
UpToFace.setReadOnly(!isUpToFaceEnabled);
}
}

View File

@@ -69,12 +69,12 @@ public:
bool suggestReversed();
enum class RevolMethod {
Dimension,
Angle,
ThroughAll,
ToLast = ThroughAll,
ToFirst,
ToFace,
TwoDimensions
TwoAngles
};
protected:
@@ -95,8 +95,8 @@ protected:
/**
* Generates a [groove] of the input sketchshape and stores it in the given \a revol.
*/
void generateRevolution(TopoDS_Shape& revol,
const TopoDS_Shape& sketchshape,
void generateRevolution(TopoShape& revol,
const TopoShape& sketchshape,
const gp_Ax1& ax1,
const double angle,
const double angle2,
@@ -108,8 +108,8 @@ protected:
* Generates a [groove] of the input \a profileshape.
* It will be a stand-alone solid created with BRepFeat_MakeRevol.
*/
void generateRevolution(TopoDS_Shape& revol,
const TopoDS_Shape& baseshape,
void generateRevolution(TopoShape& revol,
const TopoShape& baseshape,
const TopoDS_Shape& profileshape,
const TopoDS_Face& supportface,
const TopoDS_Face& uptoface,