[PD] Support two-angle/up-to-face groove

Elements copied from revolution and pocket feature.
This commit is contained in:
Ajinkya Dahale
2022-07-25 10:18:23 +05:30
parent 796727fcd3
commit c7101d7ced
4 changed files with 324 additions and 37 deletions

View File

@@ -25,6 +25,7 @@
#ifndef _PreComp_
# include <BRepAlgoAPI_Cut.hxx>
# include <BRepPrimAPI_MakeRevol.hxx>
# include <BRepFeat_MakeRevol.hxx>
# include <gp_Lin.hxx>
# include <TopoDS.hxx>
# include <TopExp_Explorer.hxx>
@@ -41,9 +42,10 @@ using namespace PartDesign;
namespace PartDesign {
/* TRANSLATOR PartDesign::Groove */
const char* Groove::TypeEnums[]= {"Angle", "ThroughAll", "UpToFirst", "UpToFace", "TwoAngles", nullptr};
PROPERTY_SOURCE(PartDesign::Groove, PartDesign::ProfileBased)
const App::PropertyAngle::Constraints Groove::floatAngle = { Base::toDegrees<double>(Precision::Angular()), 360.0, 1.0 };
@@ -52,11 +54,15 @@ Groove::Groove()
{
addSubType = FeatureAddSub::Subtractive;
ADD_PROPERTY_TYPE(Base,(Base::Vector3d(0.0f,0.0f,0.0f)),"Groove", App::Prop_ReadOnly, "Base");
ADD_PROPERTY_TYPE(Axis,(Base::Vector3d(0.0f,1.0f,0.0f)),"Groove", App::Prop_ReadOnly, "Axis");
ADD_PROPERTY_TYPE(Angle,(360.0),"Groove", App::Prop_None, "Angle");
ADD_PROPERTY_TYPE(Type, (0L), "Groove", App::Prop_None, "Groove type");
Type.setEnums(TypeEnums);
ADD_PROPERTY_TYPE(Base, (Base::Vector3d(0.0f,0.0f,0.0f)), "Groove", App::Prop_ReadOnly, "Base");
ADD_PROPERTY_TYPE(Axis, (Base::Vector3d(0.0f,1.0f,0.0f)), "Groove", App::Prop_ReadOnly, "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(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::PropertyType)(App::Prop_None), "Reference axis of Groove");
}
short Groove::mustExecute() const
@@ -65,7 +71,9 @@ short Groove::mustExecute() const
ReferenceAxis.isTouched() ||
Axis.isTouched() ||
Base.isTouched() ||
Angle.isTouched())
UpToFace.isTouched() ||
Angle.isTouched() ||
Angle2.isTouched())
return 1;
return ProfileBased::mustExecute();
}
@@ -73,17 +81,16 @@ short Groove::mustExecute() const
App::DocumentObjectExecReturn *Groove::execute()
{
// Validate parameters
double angle = Angle.getValue();
if (angle > 360.0)
// All angles are in radians unless explicitly stated
double angleDeg = Angle.getValue();
if (angleDeg > 360.0)
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of groove too large"));
angle = Base::toRadians<double>(angle);
double angle = Base::toRadians<double>(angleDeg);
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);
double angle2 = Base::toRadians(Angle2.getValue());
TopoDS_Shape sketchshape;
try {
@@ -105,7 +112,12 @@ App::DocumentObjectExecReturn *Groove::execute()
return new App::DocumentObjectExecReturn(text);
}
updateAxis();
// update Axis from ReferenceAxis
try {
updateAxis();
} catch (const Base::Exception& e) {
return new App::DocumentObjectExecReturn(e.what());
}
// get revolve axis
Base::Vector3d b = Base.getValue();
@@ -117,13 +129,7 @@ App::DocumentObjectExecReturn *Groove::execute()
if (sketchshape.IsNull())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed"));
// 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);
}
RevolMethod method = methodFromString(Type.getValueAsString());
this->positionByPrevious();
TopLoc_Location invObjLoc = this->getLocation().Inverted();
@@ -140,11 +146,61 @@ App::DocumentObjectExecReturn *Groove::execute()
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch"));
}
// revolve the face to a solid
BRepPrimAPI_MakeRevol RevolMaker(sketchshape, gp_Ax1(pnt, dir), angle);
// Create a fresh support even when base exists so that it can be used for patterns
TopoDS_Shape result;
TopoDS_Face supportface = getSupportFace();
supportface.Move(invObjLoc);
if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst) {
TopoDS_Face upToFace;
if (method == RevolMethod::ToFace) {
getFaceFromLinkSub(upToFace, UpToFace);
upToFace.Move(invObjLoc);
}
else
throw Base::RuntimeError("ProfileBased: Groove up to first is not yet supported");
// TODO: This method is designed for extrusions. needs to be adapted for grooves.
// getUpToFace(upToFace, base, supportface, sketchshape, method, dir);
TopoDS_Face supportface = getSupportFace();
supportface.Move(invObjLoc);
if (Reversed.getValue())
dir.Reverse();
TopExp_Explorer Ex(supportface,TopAbs_WIRE);
if (!Ex.More())
supportface = TopoDS_Face();
RevolMode mode = RevolMode::CutFromBase;
generateRevolution(result, base, sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True);
result = refineShapeIfActive(result);
// the result we get here is the shape _after_ the operation is done
// And the really expensive way to get the SubShape...
BRepAlgoAPI_Cut mkCut(base, result);
if (!mkCut.IsDone())
return new App::DocumentObjectExecReturn("Groove: Up to face: Could not get SubShape!");
// FIXME: In some cases this affects the Shape property: It is set to the same shape as the SubShape!!!!
TopoDS_Shape subshape = refineShapeIfActive(mkCut.Shape());
this->AddSubShape.setValue(subshape);
int resultCount = countSolids(result);
if (resultCount > 1) {
return new App::DocumentObjectExecReturn("Groove: Result has multiple solids. This is not supported at this time.");
}
this->Shape.setValue(getSolid(result));
}
else {
bool midplane = Midplane.getValue();
bool reversed = Reversed.getValue();
generateRevolution(result, sketchshape, gp_Ax1(pnt, dir), angle, angle2, midplane, reversed, method);
if (result.IsNull())
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!"));
if (RevolMaker.IsDone()) {
TopoDS_Shape result = RevolMaker.Shape();
// set the subtractive shape property for later usage in e.g. pattern
result = refineShapeIfActive(result);
this->AddSubShape.setValue(result);
@@ -167,10 +223,10 @@ App::DocumentObjectExecReturn *Groove::execute()
if (solidCount > 1) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported."));
}
}
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;
}
@@ -207,4 +263,155 @@ void Groove::updateAxis()
}
}
Groove::RevolMethod Groove::methodFromString(const std::string& methodStr)
{
if (methodStr == "Angle")
return RevolMethod::Dimension;
if (methodStr == "UpToLast")
return RevolMethod::ToLast;
if (methodStr == "ThroughAll")
return RevolMethod::ThroughAll;
if (methodStr == "UpToFirst")
return RevolMethod::ToFirst;
if (methodStr == "UpToFace")
return RevolMethod::ToFace;
if (methodStr == "TwoAngles")
return RevolMethod::TwoDimensions;
throw Base::ValueError("Groove:: No such method");
return RevolMethod::Dimension;
}
void Groove::generateRevolution(TopoDS_Shape& revol,
const TopoDS_Shape& sketchshape,
const gp_Ax1& axis,
const double angle,
const double angle2,
const bool midplane,
const bool reversed,
RevolMethod method)
{
if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) {
double angleTotal = angle;
double angleOffset = 0.;
if (method == RevolMethod::TwoDimensions) {
// Rotate the face by `angle2`/`angle` to get "second" angle
angleTotal += angle2;
angleOffset = angle2 * -1.0;
}
else if (method == RevolMethod::ThroughAll) {
angleTotal = 2 * M_PI;
}
else if (midplane) {
// Rotate the face by half the angle to get Groove 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);
}
else if (reversed) {
angleTotal *= -1.0;
}
// 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 {
std::stringstream str;
str << "ProfileBased: Internal error: Unknown method for generateGroove()";
throw Base::RuntimeError(str.str());
}
}
void Groove::generateRevolution(TopoDS_Shape& revol,
const TopoDS_Shape& baseshape,
const TopoDS_Shape& profileshape,
const TopoDS_Face& supportface,
const TopoDS_Face& uptoface,
const gp_Ax1& axis,
RevolMethod method,
RevolMode Mode,
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;
}
else {
std::stringstream str;
str << "ProfileBased: Internal error: Unknown method for generateRevolution()";
throw Base::RuntimeError(str.str());
}
}
void Groove::updateProperties(RevolMethod method)
{
// disable settings that are not valid on the current method
// disable everything unless we are sure we need it
bool isAngleEnabled = false;
bool isAngle2Enabled = false;
bool isMidplaneEnabled = false;
bool isReversedEnabled = false;
bool isUpToFaceEnabled = false;
if (method == RevolMethod::Dimension) {
isAngleEnabled = true;
isMidplaneEnabled = true;
isReversedEnabled = !Midplane.getValue();
}
else if (method == RevolMethod::ToLast) {
isReversedEnabled = true;
}
else if (method == RevolMethod::ThroughAll) {
isMidplaneEnabled = true;
isReversedEnabled = !Midplane.getValue();
}
else if (method == RevolMethod::ToFirst) {
isReversedEnabled = true;
}
else if (method == RevolMethod::ToFace) {
isReversedEnabled = true;
isUpToFaceEnabled = true;
}
else if (method == RevolMethod::TwoDimensions) {
isAngleEnabled = true;
isAngle2Enabled = true;
isReversedEnabled = true;
}
Angle.setReadOnly(!isAngleEnabled);
Angle2.setReadOnly(!isAngle2Enabled);
Midplane.setReadOnly(!isMidplaneEnabled);
Reversed.setReadOnly(!isReversedEnabled);
UpToFace.setReadOnly(!isUpToFaceEnabled);
}
}

View File

@@ -37,9 +37,11 @@ class PartDesignExport Groove : public ProfileBased
public:
Groove();
App::PropertyEnumeration Type;
App::PropertyVector Base;
App::PropertyVector Axis;
App::PropertyAngle Angle;
App::PropertyAngle Angle2;
/** if this property is set to a valid link, both Axis and Base properties
* are calculated according to the linked line
@@ -65,11 +67,64 @@ public:
/// suggests a value for Reversed flag so that material is always removed from the support
bool suggestReversed();
enum class RevolMethod {
Dimension,
ThroughAll,
ToLast = ThroughAll,
ToFirst,
ToFace,
TwoDimensions
};
protected:
/// updates Axis from ReferenceAxis
void updateAxis();
static const App::PropertyAngle::Constraints floatAngle;
// See BRepFeat_MakeRevol
enum RevolMode {
CutFromBase = 0,
FuseWithBase = 1,
None = 2
};
RevolMethod methodFromString(const std::string& methodStr);
/**
* Generates a [groove] of the input sketchshape and stores it in the given \a revol.
*/
void generateRevolution(TopoDS_Shape& revol,
const TopoDS_Shape& sketchshape,
const gp_Ax1& ax1,
const double angle,
const double angle2,
const bool midplane,
const bool reversed,
RevolMethod method);
/**
* 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,
const TopoDS_Shape& profileshape,
const TopoDS_Face& supportface,
const TopoDS_Face& uptoface,
const gp_Ax1& ax1,
RevolMethod method,
RevolMode Mode,
Standard_Boolean Modify);
/**
* Disables settings that are not valid for the current method
*/
void updateProperties(RevolMethod method);
private:
static const char* TypeEnums[];
};
} //namespace PartDesign

View File

@@ -49,7 +49,8 @@ using namespace Gui;
TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider* RevolutionView, QWidget *parent)
: TaskSketchBasedParameters(RevolutionView, parent, "PartDesign_Revolution", tr("Revolution parameters")),
ui(new Ui_TaskRevolutionParameters),
proxy(new QWidget(this))
proxy(new QWidget(this)),
isGroove(false)
{
// we need a separate container widget to add all controls to
ui->setupUi(proxy);
@@ -68,11 +69,15 @@ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider*
ui->revolveAngle2->bind(rev->Angle2);
}
else if (auto *rev = dynamic_cast<PartDesign::Groove *>(vp->getObject())) {
isGroove = true;
this->propAngle = &(rev->Angle);
this->propAngle2 = &(rev->Angle2);
this->propMidPlane = &(rev->Midplane);
this->propReferenceAxis = &(rev->ReferenceAxis);
this->propReversed = &(rev->Reversed);
this->propUpToFace = &(rev->UpToFace);
ui->revolveAngle->bind(rev->Angle);
ui->revolveAngle2->bind(rev->Angle2);
}
else {
throw Base::TypeError("The object is neither a Groove nor a Revolution.");
@@ -103,7 +108,6 @@ TaskRevolutionParameters::TaskRevolutionParameters(PartDesignGui::ViewProvider*
void TaskRevolutionParameters::setupDialog()
{
PartDesign::ProfileBased* pcFeat = static_cast<PartDesign::ProfileBased*>(vp->getObject());
ui->checkBoxMidplane->setChecked(propMidPlane->getValue());
ui->checkBoxReversed->setChecked(propReversed->getValue());
@@ -114,7 +118,7 @@ void TaskRevolutionParameters::setupDialog()
int index = 0;
// TODO: This should also be implemented for groove
if (pcFeat->isDerivedFrom(PartDesign::Revolution::getClassTypeId())) {
if (!isGroove) {
PartDesign::Revolution* rev = static_cast<PartDesign::Revolution*>(vp->getObject());
ui->revolveAngle2->setValue(propAngle2->getValue());
ui->revolveAngle2->setMaximum(propAngle2->getMaximum());
@@ -122,6 +126,14 @@ void TaskRevolutionParameters::setupDialog()
index = rev->Type.getValue();
}
else {
PartDesign::Groove* rev = static_cast<PartDesign::Groove*>(vp->getObject());
ui->revolveAngle2->setValue(propAngle2->getValue());
ui->revolveAngle2->setMaximum(propAngle2->getMaximum());
ui->revolveAngle2->setMinimum(propAngle2->getMinimum());
index = rev->Type.getValue();
}
translateModeList(index);
}
@@ -130,7 +142,12 @@ void TaskRevolutionParameters::translateModeList(int index)
{
ui->changeMode->clear();
ui->changeMode->addItem(tr("Dimension"));
ui->changeMode->addItem(tr("To last"));
if (!isGroove) {
ui->changeMode->addItem(tr("To last"));
}
else {
ui->changeMode->addItem(tr("Through all"));
}
ui->changeMode->addItem(tr("To first"));
ui->changeMode->addItem(tr("Up to face"));
ui->changeMode->addItem(tr("Two dimensions"));
@@ -448,26 +465,33 @@ void TaskRevolutionParameters::onReversed(bool on)
void TaskRevolutionParameters::onModeChanged(int index)
{
PartDesign::Revolution* pcRevolution = static_cast<PartDesign::Revolution*>(vp->getObject());
App::PropertyEnumeration* pcType;
if (!isGroove)
pcType = &(static_cast<PartDesign::Revolution*>(vp->getObject())->Type);
else
pcType = &(static_cast<PartDesign::Groove*>(vp->getObject())->Type);
switch (static_cast<PartDesign::Revolution::RevolMethod>(index)) {
case PartDesign::Revolution::RevolMethod::Dimension:
pcRevolution->Type.setValue("Angle");
pcType->setValue("Angle");
// Avoid error message
// if (ui->revolveAngle->value() < Base::Quantity(Precision::Angular(), Base::Unit::Angle)) // TODO: Ensure radians/degree consistency
// ui->revolveAngle->setValue(5.0);
break;
case PartDesign::Revolution::RevolMethod::ToLast:
pcRevolution->Type.setValue("UpToLast");
if (!isGroove)
pcType->setValue("UpToLast");
else
pcType->setValue("ThroughAll");
break;
case PartDesign::Revolution::RevolMethod::ToFirst:
pcRevolution->Type.setValue("UpToFirst");
pcType->setValue("UpToFirst");
break;
case PartDesign::Revolution::RevolMethod::ToFace:
pcRevolution->Type.setValue("UpToFace");
pcType->setValue("UpToFace");
break;
case PartDesign::Revolution::RevolMethod::TwoDimensions:
pcRevolution->Type.setValue("TwoAngles");
pcType->setValue("TwoAngles");
break;
}

View File

@@ -99,6 +99,7 @@ private:
std::unique_ptr<Ui_TaskRevolutionParameters> ui;
QWidget *proxy;
bool selectionFace;
bool isGroove;
/**
* @brief axesInList is the list of links corresponding to axis combo; must