For several PD features the single-solid check fails. The regression
is caused by PR 13960 and reported as issue 19002. The reason for the
failure is that the first solid of the output shape is retrieved and
then checked for a single solid. This test will always pass, of course.
The single-solid is fixed for these features:
* Pad
* Pocket (never worked there)
* Fillet
* Chamfer
* Groove (never worked there)
* Revolution (never worked there)
* Loft
Fixes: 935bdf9a0f ("PartDesign: Refactor single-solid rule enforcement")
387 lines
15 KiB
C++
387 lines
15 KiB
C++
/******************************************************************************
|
|
* Copyright (c) 2012 Jan Rheinländer <jrheinlaender@users.sourceforge.net> *
|
|
* *
|
|
* 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 <Mod/Part/App/FCBRepAlgoAPI_Cut.h>
|
|
# include <BRepPrimAPI_MakeRevol.hxx>
|
|
# include <BRepFeat_MakeRevol.hxx>
|
|
# include <gp_Lin.hxx>
|
|
# include <TopoDS.hxx>
|
|
# include <TopExp_Explorer.hxx>
|
|
# include <Precision.hxx>
|
|
#endif
|
|
|
|
#include <Base/Exception.h>
|
|
#include <Base/Tools.h>
|
|
|
|
#include "FeatureGroove.h"
|
|
#include "Mod/Part/App/TopoShapeOpCode.h"
|
|
|
|
|
|
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 };
|
|
|
|
Groove::Groove()
|
|
{
|
|
addSubType = FeatureAddSub::Subtractive;
|
|
|
|
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::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(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");
|
|
}
|
|
|
|
short Groove::mustExecute() const
|
|
{
|
|
if (Placement.isTouched() ||
|
|
ReferenceAxis.isTouched() ||
|
|
Axis.isTouched() ||
|
|
Base.isTouched() ||
|
|
UpToFace.isTouched() ||
|
|
Angle.isTouched() ||
|
|
Angle2.isTouched())
|
|
return 1;
|
|
return ProfileBased::mustExecute();
|
|
}
|
|
|
|
App::DocumentObjectExecReturn *Groove::execute()
|
|
{
|
|
if (onlyHaveRefined()) { return App::DocumentObject::StdReturn; }
|
|
|
|
// 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());
|
|
}
|
|
|
|
// if the Base property has a valid shape, fuse the prism 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);
|
|
}
|
|
|
|
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);
|
|
|
|
try {
|
|
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);
|
|
}
|
|
|
|
this->positionByPrevious();
|
|
auto invObjLoc = getLocation().Inverted();
|
|
pnt.Transform(invObjLoc.Transformation());
|
|
dir.Transform(invObjLoc.Transformation());
|
|
base.move(invObjLoc);
|
|
sketchshape.move(invObjLoc);
|
|
|
|
// 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;
|
|
}
|
|
// 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: that is not currently supported."));
|
|
}
|
|
boolOp = getSolid(boolOp);
|
|
Shape.setValue(boolOp);
|
|
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
|
|
return new App::DocumentObjectExecReturn(e.GetMessageString());
|
|
}
|
|
catch (Base::Exception& e) {
|
|
return new App::DocumentObjectExecReturn(e.what());
|
|
}
|
|
}
|
|
|
|
bool Groove::suggestReversed()
|
|
{
|
|
updateAxis();
|
|
return ProfileBased::getReversedAngle(Base.getValue(), Axis.getValue()) > 0.0;
|
|
}
|
|
|
|
void Groove::updateAxis()
|
|
{
|
|
App::DocumentObject *pcReferenceAxis = ReferenceAxis.getValue();
|
|
const std::vector<std::string> &subReferenceAxis = ReferenceAxis.getSubValues();
|
|
Base::Vector3d base;
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 * std::numbers::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);
|
|
}
|
|
|
|
|
|
}
|