PartDesign: Extrude 2 sides (#21794)

* PartDesign: extrude 2 sides

* Part: OpCodes XOR

* PartDesign: Remove deprecated generatePrism functions

* PartDesign: Extrude : Update Sides combobox strings

* Change "Sides" to "Mode"

* Use OpCodes::Extrude instead of Prism.
This commit is contained in:
PaddleStroke
2025-08-25 19:17:23 +02:00
committed by GitHub
parent 3b3516de71
commit a346c266e7
22 changed files with 1656 additions and 1258 deletions

View File

@@ -1422,6 +1422,35 @@ public:
return TopoShape(0, Hasher).makeElementCut({*this, source}, op, tol);
}
/** Make a boolean xor of this shape with an input shape
*
* @param source: the source shape
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param tol: tolerance for the fusion
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape&
makeElementXor(const std::vector<TopoShape>& sources, const char* op = nullptr, double tol = -1.0);
/** Make a boolean xor of this shape with an input shape
*
* @param source: the source shape
* @param op: optional string to be encoded into topo naming for indicating
* the operation
* @param tol: tolerance for the fusion
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape
makeElementXor(const TopoShape& source, const char* op = nullptr, double tol = -1.0) const
{
return TopoShape(0, Hasher).makeElementXor({*this, source}, op, tol);
}
/** Try to simplify geometry of any linear/planar subshape to line/plane
*
* @return Return true if the shape is modified

View File

@@ -4143,6 +4143,84 @@ TopoShape::makeElementCut(const std::vector<TopoShape>& shapes, const char* op,
return makeElementBoolean(Part::OpCodes::Cut, shapes, op, tol);
}
TopoShape&
TopoShape::makeElementXor(const std::vector<TopoShape>& shapes, const char* op, double tol)
{
if (shapes.empty()) {
FC_THROWM(NullShapeException, "Null shape");
}
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
FC_THROWM(Base::CADKernelError, "User aborted");
}
if (!op) {
op = Part::OpCodes::Xor;
}
std::vector<TopoShape> expandedShapes;
// Same compound expansion as Fuse
for (auto it = shapes.begin(); it != shapes.end(); ++it) {
auto& shape = *it;
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null input shape for XOR operation");
}
if (shape.shapeType() == TopAbs_COMPOUND) {
if (expandedShapes.empty()) {
expandedShapes.insert(expandedShapes.end(), shapes.begin(), it);
}
expandCompound(shape, expandedShapes);
}
else if (!expandedShapes.empty()) {
expandedShapes.push_back(shape);
}
}
const auto& inputs = expandedShapes.empty() ? shapes : expandedShapes;
// Note: The inputs.empty() check is now redundant because of the check at the top,
// but it's harmless to leave it.
if (inputs.empty()) {
FC_THROWM(NullShapeException, "Null shape");
}
if (inputs.size() == 1) {
*this = inputs[0];
if (shapes.size() == 1) {
FC_WARN("Boolean operation with only one shape input");
}
return *this;
}
TopoShape result = inputs[0];
for (size_t i = 1; i < inputs.size(); ++i) {
// The final op is only applied on the very last iteration.
const char* currentOp = (i == inputs.size() - 1) ? op : nullptr;
// Step 1: Union(A, B) - intermediate result, no op code.
TopoShape tempUnion(0, Hasher);
tempUnion.makeElementBoolean(Part::OpCodes::Fuse, {result, inputs[i]}, nullptr, tol);
// Step 2: Common(A, B) - intermediate result, no op code.
TopoShape tempCommon(0, Hasher);
tempCommon.makeElementBoolean(Part::OpCodes::Common, {result, inputs[i]}, nullptr, tol);
// Step 3: Compute the final result for this iteration
if (tempCommon.isNull() || tempCommon.getShape().IsNull()) {
// No intersection, XOR is the same as Union.
// We still call the boolean op to get the correct history.
result.makeElementBoolean(Part::OpCodes::Fuse, {result, inputs[i]}, currentOp, tol);
}
else {
// Final result is Cut(Union, Common).
result.makeElementBoolean(Part::OpCodes::Cut,
{tempUnion, tempCommon},
currentOp,
tol);
}
}
*this = result;
return *this;
}
TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape,
const TopoShape& source,
@@ -5686,6 +5764,10 @@ TopoShape& TopoShape::makeElementBoolean(const char* maker,
return *this;
}
if (strcmp(maker, Part::OpCodes::Xor) == 0) {
return makeElementXor(shapes, op, tolerance);
}
bool buildShell = true;
std::vector<TopoShape> _shapes;

View File

@@ -48,7 +48,8 @@ public:
static constexpr const char *Fuse = "FUS";
static constexpr const char *Cut = "CUT";
static constexpr const char *Common = "CMN";
static constexpr const char *Section = "SEC";
static constexpr const char* Section = "SEC";
static constexpr const char* Xor = "XOR";
static constexpr const char *Compound = "CMP";
static constexpr const char *Compsolid = "CSD";
static constexpr const char *Pipe = "PIP";

View File

@@ -30,6 +30,7 @@
# include <BRepFeat_MakePrism.hxx>
# include <BRepPrimAPI_MakePrism.hxx>
# include <gp_Dir.hxx>
# include <gp_Ax2.hxx>
# include <Precision.hxx>
# include <TopExp_Explorer.hxx>
# include <TopoDS_Compound.hxx>
@@ -49,6 +50,8 @@ FC_LOG_LEVEL_INIT("PartDesign", true, true)
using namespace PartDesign;
const char* FeatureExtrude::SideTypesEnums[] = {"One side", "Two sides", "Symmetric", nullptr};
PROPERTY_SOURCE(PartDesign::FeatureExtrude, PartDesign::ProfileBased)
App::PropertyQuantityConstraint::Constraints FeatureExtrude::signedLengthConstraint = {
@@ -61,7 +64,9 @@ FeatureExtrude::FeatureExtrude() = default;
short FeatureExtrude::mustExecute() const
{
if (Placement.isTouched() ||
SideType.isTouched() ||
Type.isTouched() ||
Type2.isTouched() ||
Length.isTouched() ||
Length2.isTouched() ||
TaperAngle.isTouched() ||
@@ -71,7 +76,11 @@ short FeatureExtrude::mustExecute() const
ReferenceAxis.isTouched() ||
AlongSketchNormal.isTouched() ||
Offset.isTouched() ||
UpToFace.isTouched())
Offset2.isTouched() ||
UpToFace.isTouched() ||
UpToFace2.isTouched() ||
UpToShape.isTouched() ||
UpToShape2.isTouched())
return 1;
return ProfileBased::mustExecute();
}
@@ -174,293 +183,115 @@ TopoShape FeatureExtrude::makeShellFromUpToShape(TopoShape shape, TopoShape sket
return shape;
}
// TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible.
void FeatureExtrude::generatePrism(TopoDS_Shape& prism,
const TopoDS_Shape& sketchshape,
const std::string& method,
const gp_Dir& direction,
const double L,
const double L2,
const bool midplane,
const bool reversed)
void FeatureExtrude::updateProperties()
{
if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") {
double Ltotal = L;
double Loffset = 0.;
if (method == "ThroughAll")
Ltotal = getThroughAllLength();
std::string sideTypeVal = SideType.getValueAsString();
std::string methodSide1 = Type.getValueAsString();
std::string methodSide2 = Type2.getValueAsString();
bool isLength1Enabled = false;
bool isTaper1Visible = false;
bool isUpToFace1Enabled = false;
bool isUpToShape1Enabled = false;
bool isOffset1Enabled = false;
if (method == "TwoLengths") {
Ltotal += L2;
if (reversed)
Loffset = -L;
else
Loffset = -L2;
}
else if (midplane) {
Loffset = -Ltotal / 2;
}
TopoDS_Shape from = sketchshape;
if (method == "TwoLengths" || midplane) {
gp_Trsf mov;
mov.SetTranslation(Loffset * gp_Vec(direction));
TopLoc_Location loc(mov);
from = sketchshape.Moved(loc);
}
else if (reversed) {
Ltotal *= -1.0;
}
if (fabs(Ltotal) < Precision::Confusion()) {
if (addSubType == Type::Additive)
throw Base::ValueError("Cannot create a pad with a height of zero.");
else
throw Base::ValueError("Cannot create a pocket with a depth of zero.");
}
// Without taper angle we create a prism because its shells are in every case no B-splines and can therefore
// be use as support for further features like Pads, Lofts etc. B-spline shells can break certain features,
// see e.g. https://forum.freecad.org/viewtopic.php?p=560785#p560785
// It is better not to use BRepFeat_MakePrism here even if we have a support because the
// resulting shape creates problems with Pocket
BRepPrimAPI_MakePrism PrismMaker(from, Ltotal * gp_Vec(direction), Standard_False, Standard_True); // finite prism
if (!PrismMaker.IsDone())
throw Base::RuntimeError("ProfileBased: Length: Could not extrude the sketch!");
prism = PrismMaker.Shape();
}
else {
std::stringstream str;
str << "ProfileBased: Internal error: Unknown method '"
<< method << "' for generatePrism()";
throw Base::RuntimeError(str.str());
}
}
void FeatureExtrude::generatePrism(TopoDS_Shape& prism,
const std::string& method,
const TopoDS_Shape& baseshape,
const TopoDS_Shape& profileshape,
const TopoDS_Face& supportface,
const TopoDS_Shape& uptoface,
const gp_Dir& direction,
PrismMode Mode,
Standard_Boolean Modify)
{
if (method == "UpToFirst" || method == "UpToFace") {
BRepFeat_MakePrism PrismMaker;
TopoDS_Shape base = baseshape;
for (TopExp_Explorer xp(profileshape, TopAbs_FACE); xp.More(); xp.Next()) {
PrismMaker.Init(base, xp.Current(), supportface, direction, Mode, Modify);
PrismMaker.Perform(uptoface);
if (!PrismMaker.IsDone())
throw Base::RuntimeError("ProfileBased: Up to face: Could not extrude the sketch!");
base = PrismMaker.Shape();
if (Mode == PrismMode::None)
Mode = PrismMode::FuseWithBase;
}
prism = base;
}
else if (method == "UpToLast") {
BRepFeat_MakePrism PrismMaker;
prism = baseshape;
for (TopExp_Explorer xp(profileshape, TopAbs_FACE); xp.More(); xp.Next()) {
PrismMaker.Init(baseshape, xp.Current(), supportface, direction, PrismMode::None, Modify);
//Each face needs 2 prisms because if uptoFace is intersected twice the first one ends too soon
for (int i=0; i<2; i++){
if (i==0){
PrismMaker.Perform(uptoface);
}else{
PrismMaker.Perform(uptoface, uptoface);
}
if (!PrismMaker.IsDone())
throw Base::RuntimeError("ProfileBased: Up to face: Could not extrude the sketch!");
auto onePrism = PrismMaker.Shape();
FCBRepAlgoAPI_Fuse fuse(prism, onePrism);
prism = fuse.Shape();
}
}
}
else {
std::stringstream str;
str << "ProfileBased: Internal error: Unknown method '"
<< method << "' for generatePrism()";
throw Base::RuntimeError(str.str());
}
}
void FeatureExtrude::generatePrism(TopoShape& prism,
TopoShape sketchTopoShape,
const std::string& method,
const gp_Dir& dir,
const double L,
const double L2,
const bool midplane,
const bool reversed)
{
auto sketchShape = sketchTopoShape.getShape();
if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") {
double Ltotal = L;
double Loffset = 0.;
if (method == "ThroughAll") {
Ltotal = getThroughAllLength();
}
if (method == "TwoLengths") {
Ltotal += L2;
if (reversed) {
Loffset = -L;
}
else {
Loffset = -L2;
}
}
else if (midplane) {
Loffset = -Ltotal / 2;
}
if (method == "TwoLengths" || midplane) {
gp_Trsf mov;
mov.SetTranslation(Loffset * gp_Vec(dir));
TopLoc_Location loc(mov);
sketchTopoShape.move(loc);
}
else if (reversed) {
Ltotal *= -1.0;
}
// Without taper angle we create a prism because its shells are in every case no B-splines
// and can therefore be use as support for further features like Pads, Lofts etc. B-spline
// shells can break certain features, see e.g.
// https://forum.freecad.org/viewtopic.php?p=560785#p560785 It is better not to use
// BRepFeat_MakePrism here even if we have a support because the resulting shape creates
// problems with Pocket
try {
prism.makeElementPrism(sketchTopoShape, Ltotal * gp_Vec(dir)); // finite prism
}
catch (Standard_Failure&) {
throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!");
}
}
else {
std::stringstream str;
str << "FeatureExtrusion: Internal error: Unknown method '" << method
<< "' for generatePrism()";
throw Base::RuntimeError(str.str());
}
}
void FeatureExtrude::generateTaperedPrism(TopoDS_Shape& prism,
const TopoDS_Shape& sketchshape,
const std::string& method,
const gp_Dir& direction,
const double L,
const double L2,
const double angle,
const double angle2,
const bool midplane)
{
std::list<TopoDS_Shape> drafts;
bool isSolid = true; // in PD we only generate solids, while Part Extrude can also create only shells
bool isPartDesign = true; // there is an OCC bug with single-edge wires (circles) we need to treat differently for PD and Part
if (method == "ThroughAll") {
Part::ExtrusionHelper::makeDraft(sketchshape, direction, getThroughAllLength(),
0.0, Base::toRadians(angle), 0.0, isSolid, drafts, isPartDesign);
}
else if (method == "TwoLengths") {
Part::ExtrusionHelper::makeDraft(sketchshape, direction, L, L2,
Base::toRadians(angle), Base::toRadians(angle2), isSolid, drafts, isPartDesign);
}
else if (method == "Length") {
if (midplane) {
Part::ExtrusionHelper::makeDraft(sketchshape, direction, L / 2, L / 2,
Base::toRadians(angle), Base::toRadians(angle), isSolid, drafts, isPartDesign);
}
else
Part::ExtrusionHelper::makeDraft(sketchshape, direction, L, 0.0,
Base::toRadians(angle), 0.0, isSolid, drafts, isPartDesign);
}
if (drafts.empty()) {
throw Base::RuntimeError("Creation of tapered object failed");
}
else if (drafts.size() == 1) {
prism = drafts.front();
}
else {
TopoDS_Compound comp;
BRep_Builder builder;
builder.MakeCompound(comp);
for (const auto & draft : drafts)
builder.Add(comp, draft);
prism = comp;
}
}
void FeatureExtrude::updateProperties(const std::string &method)
{
// disable settings that are not valid on the current method
// disable everything unless we are sure we need it
bool isLengthEnabled = false;
bool isType2Enabled = false;
bool isLength2Enabled = false;
bool isOffsetEnabled = false;
bool isMidplaneEnabled = false;
bool isReversedEnabled = false;
bool isUpToFaceEnabled = false;
bool isUpToShapeEnabled = false;
bool isTaperVisible = false;
bool isTaper2Visible = false;
if (method == "Length") {
isLengthEnabled = true;
isTaperVisible = true;
isMidplaneEnabled = true;
isReversedEnabled = !Midplane.getValue();
bool isUpToFace2Enabled = false;
bool isUpToShape2Enabled = false;
bool isOffset2Enabled = false;
bool currentAlongSketchNormalEnabled = false;
auto configureSideProperties = [&](const std::string& method,
bool& lengthEnabled,
bool& taperVisible,
bool& upToFaceEnabled,
bool& upToShapeEnabled,
bool& localAlongSketchNormal,
bool& localOffset) {
if (method == "Length") {
lengthEnabled = true;
taperVisible = true;
localAlongSketchNormal = true;
}
else if (method == "UpToFace") {
upToFaceEnabled = true;
localOffset = true;
}
else if (method == "UpToShape") {
upToShapeEnabled = true;
localOffset = true;
}
else if (method == "UpToLast" || method == "UpToFirst") {
localOffset = true;
}
else if (method == "ThroughAll") {
// No specific length/taper/offset for ThroughAll type
}
};
if (sideTypeVal == "One side") {
bool side1ASN = false;
configureSideProperties(methodSide1,
isLength1Enabled,
isTaper1Visible,
isUpToFace1Enabled,
isUpToShape1Enabled,
side1ASN,
isOffset1Enabled);
currentAlongSketchNormalEnabled = side1ASN;
}
else if (method == "UpToLast") {
isOffsetEnabled = true;
isReversedEnabled = true;
else if (sideTypeVal == "Two sides") {
isType2Enabled = true;
bool side1ASN = false;
configureSideProperties(methodSide1,
isLength1Enabled,
isTaper1Visible,
isUpToFace1Enabled,
isUpToShape1Enabled,
side1ASN,
isOffset1Enabled);
bool side2ASN = false;
configureSideProperties(methodSide2,
isLength2Enabled,
isTaper2Visible,
isUpToFace2Enabled,
isUpToShape2Enabled,
side2ASN,
isOffset2Enabled);
currentAlongSketchNormalEnabled = side1ASN || side2ASN; // Enable if either side needs it
}
else if (method == "ThroughAll") {
isMidplaneEnabled = true;
isReversedEnabled = !Midplane.getValue();
}
else if (method == "UpToFirst") {
isOffsetEnabled = true;
isReversedEnabled = true;
}
else if (method == "UpToFace") {
isOffsetEnabled = true;
isReversedEnabled = true;
isUpToFaceEnabled = true;
}
else if (method == "TwoLengths") {
isLengthEnabled = true;
isLength2Enabled = true;
isTaperVisible = true;
isTaper2Visible = true;
isReversedEnabled = true;
}
else if (method == "UpToShape") {
isReversedEnabled = true;
isUpToShapeEnabled = true;
else if (sideTypeVal == "Symmetric") {
bool symASN = false;
configureSideProperties(methodSide1,
isLength1Enabled,
isTaper1Visible,
isUpToFace1Enabled,
isUpToShape1Enabled,
symASN,
isOffset1Enabled);
currentAlongSketchNormalEnabled = symASN;
}
Length.setReadOnly(!isLengthEnabled);
AlongSketchNormal.setReadOnly(!isLengthEnabled);
Length.setReadOnly(!isLength1Enabled);
TaperAngle.setReadOnly(!isTaper1Visible);
UpToFace.setReadOnly(!isUpToFace1Enabled);
UpToShape.setReadOnly(!isUpToShape1Enabled);
Offset.setReadOnly(!isOffset1Enabled);
Type2.setReadOnly(!isType2Enabled);
Length2.setReadOnly(!isLength2Enabled);
Offset.setReadOnly(!isOffsetEnabled);
TaperAngle.setReadOnly(!isTaperVisible);
TaperAngle2.setReadOnly(!isTaper2Visible);
Midplane.setReadOnly(!isMidplaneEnabled);
Reversed.setReadOnly(!isReversedEnabled);
UpToFace.setReadOnly(!isUpToFaceEnabled);
UpToShape.setReadOnly(!isUpToShapeEnabled);
UpToFace2.setReadOnly(!isUpToFace2Enabled);
UpToShape2.setReadOnly(!isUpToShape2Enabled);
Offset2.setReadOnly(!isOffset2Enabled);
AlongSketchNormal.setReadOnly(!currentAlongSketchNormalEnabled);
}
void FeatureExtrude::setupObject()
@@ -475,23 +306,30 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
bool makeface = options.testFlag(ExtrudeOption::MakeFace);
bool fuse = options.testFlag(ExtrudeOption::MakeFuse);
bool legacyPocket = options.testFlag(ExtrudeOption::LegacyPocket);
bool inverseDirection = options.testFlag(ExtrudeOption::InverseDirection);
std::string Sidemethod(SideType.getValueAsString());
std::string method(Type.getValueAsString());
std::string method2(Type2.getValueAsString());
// Validate parameters
double L = Length.getValue();
if ((method == "Length") && (L < Precision::Confusion())) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Length too small"));
}
double L2 = 0;
if ((method == "TwoLengths")) {
L2 = Length2.getValue();
if (std::abs(L2) < Precision::Confusion()) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Second length too small"));
double L2 = (Sidemethod == "Two sides" && method2 == "Length") ? Length2.getValue() : 0;
if ((Sidemethod == "One side" && method == "Length")
|| (Sidemethod == "Two sides" && method == "Length" && method2 == "Length")) {
if (std::abs(L + L2) < Precision::Confusion()) {
if (addSubType == Type::Additive) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception",
"Cannot create a pad with a total length of zero."));
}
else {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception",
"Cannot create a pocket with a total length of zero."));
}
}
}
@@ -598,6 +436,9 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
Direction.setValue(paddingDirection);
dir.Transform(invTrsf);
if (Reversed.getValue()) {
dir.Reverse();
}
if (sketchshape.isNull()) {
return new App::DocumentObjectExecReturn(
@@ -605,174 +446,96 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
}
sketchshape.move(invObjLoc);
TopoShape prism(0, getDocument()->getStringHasher());
std::vector<TopoShape> prisms; // Stores prisms, all in global CS
std::string sideTypeStr = SideType.getValueAsString();
std::string method1 = Type.getValueAsString();
double len1 = method1 == "ThroughAll" ? getThroughAllLength() : Length.getValue();
double taper1 = TaperAngle.getValue();
double offset1 = Offset.getValue();
if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace" || method == "UpToShape") {
// Note: This will return an unlimited planar face if support is a datum plane
TopoShape supportface = getTopoShapeSupportFace();
supportface.move(invObjLoc);
if (sideTypeStr == "One side") {
TopoShape prism1 = generateSingleExtrusionSide(sketchshape,
method1, len1, taper1, UpToFace, UpToShape,
dir, offset1, makeface, base);
prisms.push_back(prism1);
}
else if (sideTypeStr == "Symmetric") {
TopoShape prism1 = generateSingleExtrusionSide(sketchshape,
method1, len1, taper1, UpToFace, UpToShape,
dir, offset1, makeface, base);
prisms.push_back(prism1);
if (Reversed.getValue()) {
dir.Reverse();
// Prism 2 : Make a symmetric of prism1
Base::Vector3d base = sketchshape.getBoundBox().GetCenter();
gp_Ax2 axe(gp_Pnt(base.x, base.y, base.z), dir);
TopoShape prism2 = prism1.makeElementMirror(axe);
prisms.push_back(prism2);
}
else if (sideTypeStr == "Two sides") {
TopoShape prism1 = generateSingleExtrusionSide(sketchshape.makeElementCopy(),
method1,
len1,
taper1,
UpToFace,
UpToShape,
dir,
offset1,
makeface,
base);
if (!prism1.isNull() && !prism1.getShape().IsNull()) {
prisms.push_back(prism1);
}
TopoShape upToShape;
int faceCount = 1;
// Find a valid shape, face or datum plane to extrude up to
if (method == "UpToFace") {
getUpToFaceFromLinkSub(upToShape, UpToFace);
upToShape.move(invObjLoc);
faceCount = 1;
}
else if (method == "UpToShape") {
faceCount = getUpToShapeFromLinkSubList(upToShape, UpToShape);
upToShape.move(invObjLoc);
if (faceCount == 0){
// No shape selected, use the base
upToShape = base;
faceCount = 0;
}
}
// Side 2
std::string method2 = Type2.getValueAsString();
double len2 = method2 == "ThroughAll" ? getThroughAllLength() : Length2.getValue();
double taper2 = TaperAngle2.getValue();
double offset2 = Offset2.getValue();
gp_Dir dir2 = dir;
dir2.Reverse();
if (faceCount == 1) {
getUpToFace(upToShape, base, sketchshape, method, dir);
addOffsetToFace(upToShape, dir, Offset.getValue());
}
else{
if (fabs(Offset.getValue()) > Precision::Confusion()){
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Extrude: Can only offset one face"));
}
// open the shell by removing the furthest face
upToShape = makeShellFromUpToShape(upToShape, sketchshape, dir);
}
if (!supportface.hasSubShape(TopAbs_WIRE)) {
supportface = TopoShape();
}
if (legacyPocket) {
auto mode =
base.isNull() ? TopoShape::PrismMode::None : TopoShape::PrismMode::CutFromBase;
prism = base.makeElementPrismUntil(sketchshape,
supportface,
upToShape,
dir,
mode,
false /*CheckUpToFaceLimits.getValue()*/);
// DO NOT assign id to the generated prism, because this prism is
// actually the final result. We obtain the subtracted shape by cut
// this prism with the original base. Assigning a minus self id here
// will mess up with preselection highlight. It is enough to re-tag
// the profile shape above.
//
// prism.Tag = -this->getID();
// And the really expensive way to get the SubShape...
try {
TopoShape result(0, getDocument()->getStringHasher());
if (base.isNull()) {
result = prism;
}
else {
result.makeElementCut({base, prism});
}
// store shape before refinement
this->rawShape = result;
result = refineShapeIfActive(result);
this->AddSubShape.setValue(result);
}
catch (Standard_Failure&) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Up to face: Could not get SubShape!"));
}
if (getAddSubType() == Additive) {
prism = base.makeElementFuse(this->AddSubShape.getShape());
}
else {
// store shape before refinement
this->rawShape = prism;
prism = refineShapeIfActive(prism);
}
this->Shape.setValue(getSolid(prism));
return App::DocumentObject::StdReturn;
}
try {
TopoShape _base;
if (addSubType!=FeatureAddSub::Subtractive) {
_base=base; // avoid issue #16690
}
prism.makeElementPrismUntil(_base,
sketchshape,
supportface,
upToShape,
dir,
TopoShape::PrismMode::None,
true /*CheckUpToFaceLimits.getValue()*/);
}
catch (Base::Exception&) {
if (method == "UpToShape" && faceCount > 1){
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP(
"Exception",
"Unable to reach the selected shape, please select faces"));
}
TopoShape prism2 = generateSingleExtrusionSide(sketchshape.makeElementCopy(),
method2,
len2,
taper2,
UpToFace2,
UpToShape2,
dir2,
offset2,
makeface,
base);
if (!prism2.isNull() && !prism2.getShape().IsNull()) {
prisms.push_back(prism2);
}
}
else {
using std::numbers::pi;
Part::ExtrusionParameters params;
params.dir = dir;
params.solid = makeface;
params.taperAngleFwd = Base::toRadians(this->TaperAngle.getValue());
params.taperAngleRev = Base::toRadians(this->TaperAngle2.getValue());
if (L2 == 0.0 && Midplane.getValue()) {
params.lengthFwd = L / 2;
params.lengthRev = L / 2;
if (params.taperAngleRev == 0.0) {
params.taperAngleRev = params.taperAngleFwd;
}
// --- Combine generated prisms (all in global CS) ---
TopoShape prism(0, getDocument()->getStringHasher());
if (prisms.empty()) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "No extrusion geometry was generated."));
}
else if (prisms.size() == 1) {
prism = prisms[0];
}
else {
try {
prism.makeElementXor(prisms, Part::OpCodes::Extrude);
}
else {
params.lengthFwd = L;
params.lengthRev = L2;
catch (const Standard_Failure& e) {
return new App::DocumentObjectExecReturn(
std::string("Failed to xor extrusion sides (OCC): ") + e.GetMessageString());
}
if (std::fabs(params.taperAngleFwd) >= Precision::Angular()
|| std::fabs(params.taperAngleRev) >= Precision::Angular()) {
if (fabs(params.taperAngleFwd) > pi * 0.5 - Precision::Angular()
|| fabs(params.taperAngleRev) > pi * 0.5 - Precision::Angular()) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP(
"Exception",
"Magnitude of taper angle matches or exceeds 90 degrees"));
}
if (Reversed.getValue()) {
params.dir.Reverse();
}
std::vector<TopoShape> drafts;
Part::ExtrusionHelper::makeElementDraft(params, sketchshape, drafts, getDocument()->getStringHasher());
if (drafts.empty()) {
return new App::DocumentObjectExecReturn(
QT_TRANSLATE_NOOP("Exception", "Padding with draft angle failed"));
}
prism.makeElementCompound(
drafts,
nullptr,
TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
}
else {
generatePrism(prism,
sketchshape,
method,
dir,
L,
L2,
Midplane.getValue(),
Reversed.getValue());
catch (const Base::Exception& e) {
return new App::DocumentObjectExecReturn(
std::string("Failed to xor extrusion sides: ") + e.what());
}
}
if (prism.isNull()) {
return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Resulting fused extrusion is null."));
}
// store shape before refinement
this->rawShape = prism;
prism = refineShapeIfActive(prism);
@@ -841,7 +604,7 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
}
// eventually disable some settings that are not valid for the current method
updateProperties(method);
updateProperties();
return App::DocumentObject::StdReturn;
}
@@ -860,3 +623,134 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt
return new App::DocumentObjectExecReturn(e.what());
}
}
TopoShape FeatureExtrude::generateSingleExtrusionSide(const TopoShape& sketchshape,
const std::string& method,
double length,
double taperAngleDeg,
App::PropertyLinkSub& upToFacePropHandle,
App::PropertyLinkSubList& upToShapePropHandle,
gp_Dir dir,
double offsetVal,
bool makeFace,
const TopoShape& base)
{
TopoShape prism(0, getDocument()->getStringHasher());
if (method == "UpToFirst" || method == "UpToLast" || method == "UpToFace" || method == "UpToShape") {
// Note: This will return an unlimited planar face if support is a datum plane
TopoShape supportface = getTopoShapeSupportFace();
auto invObjLoc = getLocation().Inverted();
supportface.move(invObjLoc);
if (!supportface.hasSubShape(TopAbs_WIRE)) {
supportface = TopoShape();
}
TopoShape upToShape;
int faceCount = 1;
// Find a valid shape, face or datum plane to extrude up to
if (method == "UpToFace") {
getUpToFaceFromLinkSub(upToShape, upToFacePropHandle);
upToShape.move(invObjLoc);
}
else if (method == "UpToShape") {
faceCount = getUpToShapeFromLinkSubList(upToShape, upToShapePropHandle);
upToShape.move(invObjLoc);
if (faceCount == 0) {
// No shape selected, use the base
upToShape = base;
}
}
if (faceCount == 1) {
getUpToFace(upToShape, base, sketchshape, method, dir);
addOffsetToFace(upToShape, dir, offsetVal);
}
else {
if (fabs(offsetVal) > Precision::Confusion()) {
throw Base::RuntimeError("Extrude: Can only offset one face");
}
// open the shell by removing the furthest face
upToShape = makeShellFromUpToShape(upToShape, sketchshape, dir);
}
try {
TopoShape _base;
if (addSubType != FeatureAddSub::Subtractive) {
_base = base; // avoid issue #16690
}
prism.makeElementPrismUntil(_base,
sketchshape,
supportface,
upToShape,
dir,
TopoShape::PrismMode::None,
true /*CheckUpToFaceLimits.getValue()*/);
}
catch (Base::Exception&) {
if (method == "UpToShape" && faceCount > 1) {
throw Base::RuntimeError("Extrude: Unable to reach the selected shape, please select faces");
}
}
}
else if (method == "Length" || method == "ThroughAll") {
using std::numbers::pi;
Part::ExtrusionParameters params;
params.taperAngleFwd = Base::toRadians(taperAngleDeg);
if (std::fabs(params.taperAngleFwd) >= Precision::Angular() || std::fabs(params.taperAngleRev) >= Precision::Angular()) {
if (fabs(params.taperAngleFwd) > pi * 0.5 - Precision::Angular() || fabs(params.taperAngleRev) > pi * 0.5 - Precision::Angular()) {
return prism;
}
params.dir = dir;
params.solid = makeFace;
params.lengthFwd = length;
std::vector<TopoShape> drafts;
Part::ExtrusionHelper::makeElementDraft(params,
sketchshape,
drafts,
getDocument()->getStringHasher());
if (drafts.empty()) {
return prism;
}
prism.makeElementCompound(drafts,
nullptr,
TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
}
else {
// Without taper angle we create a prism because its shells are in every case no
// B-splines and can therefore be use as support for further features like Pads,
// Lofts etc. B-spline shells can break certain features, see e.g.
// https://forum.freecad.org/viewtopic.php?p=560785#p560785 It is better not to use
// BRepFeat_MakePrism here even if we have a support because the resulting shape
// creates problems with Pocket
try {
prism.makeElementPrism(sketchshape, length * gp_Vec(dir));
}
catch (Standard_Failure&) {
throw Base::RuntimeError("FeatureExtrusion: Length: Could not extrude the sketch!");
}
}
}
return prism;
}
void FeatureExtrude::handleChangedPropertyType(Base::XMLReader& reader,
const char* TypeName,
App::Property* prop)
{
// property Type no longer has TwoLengths.
if (prop == &Type && strcmp(Type.getValueAsString(), "TwoLengths") == 0) {
Type.setValue("Length");
Type2.setValue("Length");
SideType.setValue("Two sides");
}
else {
ProfileBased::handleChangedPropertyType(reader, TypeName, prop);
}
}

View File

@@ -42,7 +42,9 @@ class PartDesignExport FeatureExtrude : public ProfileBased
public:
FeatureExtrude();
App::PropertyEnumeration SideType;
App::PropertyEnumeration Type;
App::PropertyEnumeration Type2;
App::PropertyLength Length;
App::PropertyLength Length2;
App::PropertyAngle TaperAngle;
@@ -51,6 +53,7 @@ public:
App::PropertyVector Direction;
App::PropertyBool AlongSketchNormal;
App::PropertyLength Offset;
App::PropertyLength Offset2;
App::PropertyLinkSub ReferenceAxis;
static App::PropertyQuantityConstraint::Constraints signedLengthConstraint;
@@ -67,7 +70,10 @@ public:
}
//@}
static const char* SideTypesEnums[];
protected:
void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override;
Base::Vector3d computeDirection(const Base::Vector3d& sketchVector, bool inverse);
bool hasTaperedAngle() const;
@@ -92,65 +98,23 @@ protected:
*/
TopoShape makeShellFromUpToShape(TopoShape shape, TopoShape sketchshape, gp_Dir dir);
/**
* Generates an extrusion of the input sketchshape and stores it in the given \a prism
*/
void generatePrism(TopoDS_Shape& prism,
const TopoDS_Shape& sketchshape,
const std::string& method,
const gp_Dir& direction,
const double L,
const double L2,
const bool midplane,
const bool reversed);
void generatePrism(TopoShape& prism,
TopoShape sketchshape,
const std::string& method,
const gp_Dir& direction,
const double L,
const double L2,
const bool midplane,
const bool reversed);
// See BRepFeat_MakePrism
enum PrismMode {
CutFromBase = 0,
FuseWithBase = 1,
None = 2
};
/**
* Generates an extrusion of the input profileshape
* It will be a stand-alone solid created with BRepFeat_MakePrism
*/
static void generatePrism(TopoDS_Shape& prism,
const std::string& method,
const TopoDS_Shape& baseshape,
const TopoDS_Shape& profileshape,
const TopoDS_Face& sketchface,
const TopoDS_Shape& uptoface,
const gp_Dir& direction,
PrismMode Mode,
Standard_Boolean Modify);
/**
* Generates a tapered prism of the input sketchshape and stores it in the given \a prism
*/
void generateTaperedPrism(TopoDS_Shape& prism,
const TopoDS_Shape& sketchshape,
const std::string& method,
const gp_Dir& direction,
const double L,
const double L2,
const double angle,
const double angle2,
const bool midplane);
/**
* Disables settings that are not valid for the current method
*/
void updateProperties(const std::string &method);
void updateProperties();
TopoShape generateSingleExtrusionSide(
const TopoShape& sketchShape, // The base sketch for this side (global CS)
const std::string& method,
double length,
double taperAngleDeg,
App::PropertyLinkSub& upToFacePropHandle, // e.g., &UpToFace or &UpToFace2
App::PropertyLinkSubList& upToShapePropHandle, // e.g., &UpToShape or &UpToShape2
gp_Dir dir,
double offsetVal,
bool makeFace,
const TopoShape& base // The base shape for context (global CS)
);
};
} //namespace PartDesign

View File

@@ -38,7 +38,7 @@
using namespace PartDesign;
const char* Pad::TypeEnums[]= {"Length", "UpToLast", "UpToFirst", "UpToFace", "TwoLengths", "UpToShape", nullptr};
const char* Pad::TypeEnums[]= {"Length", "UpToLast", "UpToFirst", "UpToFace", "UpToShape", nullptr};
PROPERTY_SOURCE(PartDesign::Pad, PartDesign::FeatureExtrude)
@@ -46,25 +46,34 @@ Pad::Pad()
{
addSubType = FeatureAddSub::Additive;
ADD_PROPERTY_TYPE(Type, (0L), "Pad", App::Prop_None, "Pad type");
ADD_PROPERTY_TYPE(SideType, (0L), "Pad", App::Prop_None, "Type of sides definition");
ADD_PROPERTY_TYPE(Type, (0L), "Side1", App::Prop_None, "Pad type for side 1");
ADD_PROPERTY_TYPE(Type2, (0L), "Side2", App::Prop_None, "Pad type for side 2");
SideType.setEnums(SideTypesEnums);
Type.setEnums(TypeEnums);
ADD_PROPERTY_TYPE(Length, (10.0), "Pad", App::Prop_None, "Pad length");
ADD_PROPERTY_TYPE(Length2, (10.0), "Pad", App::Prop_None, "Pad length in 2nd direction");
Type2.setEnums(TypeEnums);
ADD_PROPERTY_TYPE(Length, (10.0), "Side1", App::Prop_None, "Pad length");
ADD_PROPERTY_TYPE(Length2, (10.0), "Side2", App::Prop_None, "Pad length in 2nd direction");
ADD_PROPERTY_TYPE(UseCustomVector, (false), "Pad", App::Prop_None, "Use custom vector for pad direction");
ADD_PROPERTY_TYPE(Direction, (Base::Vector3d(1.0, 1.0, 1.0)), "Pad", App::Prop_None, "Pad direction vector");
ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Pad", App::Prop_None, "Reference axis of direction");
ADD_PROPERTY_TYPE(AlongSketchNormal, (true), "Pad", App::Prop_None, "Measure pad length along the sketch normal direction");
ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Pad", App::Prop_None, "Face where pad will end");
ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Pad", App::Prop_None, "Faces or shape(s) where pad will end");
ADD_PROPERTY_TYPE(Offset, (0.0), "Pad", App::Prop_None, "Offset from face in which pad will end");
ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Side1", App::Prop_None, "Face where pad will end");
ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Side1", App::Prop_None, "Faces or shape(s) where pad will end");
ADD_PROPERTY_TYPE(UpToFace2, (nullptr), "Side2", App::Prop_None, "Face where pad will end on side2");
ADD_PROPERTY_TYPE(UpToShape2, (nullptr), "Side2", App::Prop_None, "Faces or shape(s) where pad will end on side2");
ADD_PROPERTY_TYPE(Offset, (0.0), "Side1", App::Prop_None, "Offset from face in which pad will end");
ADD_PROPERTY_TYPE(Offset2, (0.0), "Side2", App::Prop_None, "Offset from face in which pad will end on side 2");
Offset.setConstraints(&signedLengthConstraint);
ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Pad", App::Prop_None, "Taper angle");
Offset2.setConstraints(&signedLengthConstraint);
ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Side1", App::Prop_None, "Taper angle");
TaperAngle.setConstraints(&floatAngle);
ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Pad", App::Prop_None, "Taper angle for 2nd direction");
ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Side2", App::Prop_None, "Taper angle for 2nd direction");
TaperAngle2.setConstraints(&floatAngle);
// Remove the constraints and keep the type to allow one to accept negative values
// https://forum.freecad.org/viewtopic.php?f=3&t=52075&p=448410#p447636
Length.setConstraints(nullptr);
Length2.setConstraints(nullptr);
}

View File

@@ -45,8 +45,6 @@ public:
* that is cut by a line through the centre of gravite of the sketch
* If Type is "UpToFirst" then extrusion will stop at the first face of the support
* If Type is "UpToFace" then the extrusion will stop at FaceName in the support
* If Type is "TwoLengths" then the extrusion will extend Length in the direction away from the support
* and Length2 in the opposite direction
* If Midplane is true, then the extrusion will extend for half of the length on both sides of the sketch plane
* If Reversed is true then the direction of revolution will be reversed.
* The created material will be fused with the sketch support (if there is one)

View File

@@ -39,7 +39,7 @@ using namespace PartDesign;
/* TRANSLATOR PartDesign::Pocket */
const char* Pocket::TypeEnums[]= {"Length", "ThroughAll", "UpToFirst", "UpToFace", "TwoLengths", "UpToShape", nullptr};
const char* Pocket::TypeEnums[]= {"Length", "ThroughAll", "UpToFirst", "UpToFace", "UpToShape", nullptr};
PROPERTY_SOURCE(PartDesign::Pocket, PartDesign::FeatureExtrude)
@@ -47,25 +47,34 @@ Pocket::Pocket()
{
addSubType = FeatureAddSub::Subtractive;
ADD_PROPERTY_TYPE(Type, ((long)0), "Pocket", App::Prop_None, "Pocket type");
ADD_PROPERTY_TYPE(SideType, (0L), "Pocket", App::Prop_None, "Type of sides definition");
ADD_PROPERTY_TYPE(Type, ((long)0), "Side1", App::Prop_None, "Pocket type");
ADD_PROPERTY_TYPE(Type2, ((long)0), "Side2", App::Prop_None, "Pocket type");
SideType.setEnums(SideTypesEnums);
Type.setEnums(TypeEnums);
ADD_PROPERTY_TYPE(Length, (5.0), "Pocket", App::Prop_None, "Pocket length");
ADD_PROPERTY_TYPE(Length2, (5.0), "Pocket", App::Prop_None, "Pocket length in 2nd direction");
Type2.setEnums(TypeEnums);
ADD_PROPERTY_TYPE(Length, (5.0), "Side1", App::Prop_None, "Pocket length");
ADD_PROPERTY_TYPE(Length2, (5.0), "Side2", App::Prop_None, "Pocket length in 2nd direction");
ADD_PROPERTY_TYPE(UseCustomVector, (false), "Pocket", App::Prop_None, "Use custom vector for pocket direction");
ADD_PROPERTY_TYPE(Direction, (Base::Vector3d(1.0, 1.0, 1.0)), "Pocket", App::Prop_None, "Pocket direction vector");
ADD_PROPERTY_TYPE(ReferenceAxis, (nullptr), "Pocket", App::Prop_None, "Reference axis of direction");
ADD_PROPERTY_TYPE(AlongSketchNormal, (true), "Pocket", App::Prop_None, "Measure pocket length along the sketch normal direction");
ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Pocket", App::Prop_None, "Face where pocket will end");
ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Pocket", App::Prop_None, "Face(s) or shape(s) where pocket will end");
ADD_PROPERTY_TYPE(Offset, (0.0), "Pocket", App::Prop_None, "Offset from face in which pocket will end");
ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Side1", App::Prop_None, "Face where pocket will end");
ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Side1", App::Prop_None, "Face(s) or shape(s) where pocket will end");
ADD_PROPERTY_TYPE(UpToFace2, (nullptr), "Side2", App::Prop_None, "Face where pocket will end");
ADD_PROPERTY_TYPE(UpToShape2, (nullptr), "Side2", App::Prop_None, "Face(s) or shape(s) where pocket will end");
ADD_PROPERTY_TYPE(Offset, (0.0), "Side1", App::Prop_None, "Offset from face in which pocket will end");
ADD_PROPERTY_TYPE(Offset2, (0.0), "Side2", App::Prop_None, "Offset from face in which pocket will end on side 2");
Offset.setConstraints(&signedLengthConstraint);
ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Pocket", App::Prop_None, "Taper angle");
Offset2.setConstraints(&signedLengthConstraint);
ADD_PROPERTY_TYPE(TaperAngle, (0.0), "Side1", App::Prop_None, "Taper angle");
TaperAngle.setConstraints(&floatAngle);
ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Pocket", App::Prop_None, "Taper angle for 2nd direction");
ADD_PROPERTY_TYPE(TaperAngle2, (0.0), "Side2", App::Prop_None, "Taper angle for 2nd direction");
TaperAngle2.setConstraints(&floatAngle);
// Remove the constraints and keep the type to allow one to accept negative values
// https://forum.freecad.org/viewtopic.php?f=3&t=52075&p=448410#p447636
Length.setConstraints(nullptr);
Length2.setConstraints(nullptr);
}

View File

@@ -74,8 +74,10 @@ ProfileBased::ProfileBased()
ADD_PROPERTY_TYPE(Profile, (nullptr), "SketchBased", App::Prop_None, "Reference to sketch");
ADD_PROPERTY_TYPE(Midplane, (0), "SketchBased", App::Prop_None, "Extrude symmetric to sketch face");
ADD_PROPERTY_TYPE(Reversed, (0), "SketchBased", App::Prop_None, "Reverse extrusion direction");
ADD_PROPERTY_TYPE(UpToFace, (nullptr), "SketchBased", (App::PropertyType)(App::Prop_None), "Face where feature will end");
ADD_PROPERTY_TYPE(UpToShape, (nullptr), "SketchBased", (App::PropertyType)(App::Prop_None), "Shape where feature will end");
ADD_PROPERTY_TYPE(UpToFace, (nullptr), "Side1", (App::PropertyType)(App::Prop_None), "Face where feature will end");
ADD_PROPERTY_TYPE(UpToShape, (nullptr), "Side1", (App::PropertyType)(App::Prop_None), "Shape where feature will end");
ADD_PROPERTY_TYPE(UpToFace2, (nullptr), "Side2", (App::PropertyType)(App::Prop_None), "Face where feature will end");
ADD_PROPERTY_TYPE(UpToShape2, (nullptr), "Side2", (App::PropertyType)(App::Prop_None), "Shape where feature will end");
ADD_PROPERTY_TYPE(AllowMultiFace, (false), "SketchBased", App::Prop_None, "Allow multiple faces in profile");
}
@@ -84,7 +86,10 @@ short ProfileBased::mustExecute() const
if (Profile.isTouched() ||
Midplane.isTouched() ||
Reversed.isTouched() ||
UpToFace.isTouched())
UpToFace.isTouched() ||
UpToFace2.isTouched() ||
UpToShape.isTouched() ||
UpToShape2.isTouched())
return 1;
return PartDesign::FeatureAddSub::mustExecute();
}

View File

@@ -57,8 +57,10 @@ public:
App::PropertyBool Midplane;
/// Face to extrude up to
App::PropertyLinkSub UpToFace;
App::PropertyLinkSub UpToFace2;
/// Shape to extrude up to
App::PropertyLinkSubList UpToShape;
App::PropertyLinkSubList UpToShape2;
App::PropertyBool AllowMultiFace;

File diff suppressed because it is too large Load Diff

View File

@@ -26,11 +26,20 @@
#include "TaskSketchBasedParameters.h"
#include "ViewProviderExtrude.h"
class QCheckBox;
class QComboBox;
class QLineEdit;
class QListWidget;
class QToolButton;
class Ui_TaskPadPocketParameters;
namespace App {
class Property;
class PropertyLinkSubList;
}
namespace Gui {
class PrefQuantitySpinBox;
}
namespace PartDesign {
@@ -57,13 +66,23 @@ public:
Pocket
};
enum class SidesMode {
OneSide,
TwoSides,
Symmetric,
};
enum class Side {
First,
Second,
};
enum class Mode {
Dimension,
ThroughAll,
ToLast = ThroughAll,
ToFirst,
ToFace,
TwoDimensions,
ToShape,
};
@@ -84,35 +103,84 @@ public:
void fillDirectionCombo();
void addAxisToCombo(App::DocumentObject* linkObj, std::string linkSubname, QString itemText,
bool hasSketch = true);
void applyParameters(QString facename);
void applyParameters();
void setSelectionMode(SelectionMode mode);
void setSelectionMode(SelectionMode mode, Side side = Side::First);
protected:
// This struct holds all pointers for one side's UI and properties
struct SideController
{
// UI Widgets
QComboBox* changeMode = nullptr;
QLabel* labelLength = nullptr;
QLabel* labelOffset = nullptr;
QLabel* labelTaperAngle = nullptr;
Gui::PrefQuantitySpinBox* lengthEdit = nullptr;
Gui::PrefQuantitySpinBox* offsetEdit = nullptr;
Gui::PrefQuantitySpinBox* taperEdit = nullptr;
QLineEdit* lineFaceName = nullptr;
QToolButton* buttonFace = nullptr;
QLineEdit* lineShapeName = nullptr;
QToolButton* buttonShape = nullptr;
QListWidget* listWidgetReferences = nullptr;
QToolButton* buttonShapeFace = nullptr;
QCheckBox* checkBoxAllFaces = nullptr;
QWidget* upToShapeList = nullptr;
QWidget* upToShapeFaces = nullptr;
QAction* unselectShapeFaceAction = nullptr;
// Feature Properties
App::PropertyEnumeration* Type = nullptr;
App::PropertyLength* Length = nullptr;
App::PropertyLength* Offset = nullptr;
App::PropertyAngle* TaperAngle = nullptr;
App::PropertyLinkSub* UpToFace = nullptr;
App::PropertyLinkSubList* UpToShape = nullptr;
};
SideController m_side1;
SideController m_side2;
SideController& getSideController(Side side)
{
return (side == Side::First) ? m_side1 : m_side2;
}
protected Q_SLOTS:
void onLengthChanged(double);
void onLength2Changed(double);
void onOffsetChanged(double);
void onTaperChanged(double);
void onTaper2Changed(double);
void onSidesModeChanged(int);
virtual void onModeChanged(int index, Side side) = 0;
private Q_SLOTS:
void onDirectionCBChanged(int);
void onAlongSketchNormalChanged(bool);
void onDirectionToggled(bool);
void onAllFacesToggled(bool);
void onXDirectionEditChanged(double);
void onYDirectionEditChanged(double);
void onZDirectionEditChanged(double);
void onMidplaneChanged(bool);
void onReversedChanged(bool);
void onFaceName(const QString& text);
void onSelectFaceToggle(const bool checked = true);
void onSelectShapeToggle(const bool checked = true);
void onSelectShapeFacesToggle(const bool checked);
void onUnselectShapeFacesTrigger();
virtual void onModeChanged(int);
private:
void onModeChanged_Side1(int index);
void onModeChanged_Side2(int index);
void onLengthChanged(double len, Side side);
void onOffsetChanged(double len, Side side);
void onTaperChanged(double angle, Side side);
void onSelectFaceToggle(bool checked, Side side);
void onFaceName(const QString& text, Side side);
void onAllFacesToggled(bool checked, Side side);
void onSelectShapeToggle(bool checked, Side side);
void onSelectShapeFacesToggle(bool checked, Side side);
void onUnselectShapeFacesTrigger(Side side);
protected:
void setCheckboxes(Mode mode, Type type);
void updateWholeUI(Type type, Side side);
void updateSideUI(const SideController& s,
Type featureType,
Mode sideMode,
bool isParentVisible,
bool setFocus);
void setupDialog();
void readValuesFromHistory();
void changeEvent(QEvent *e) override;
@@ -120,6 +188,7 @@ protected:
void getReferenceAxis(App::DocumentObject*& obj, std::vector<std::string>& sub) const;
double getOffset() const;
double getOffset2() const;
bool getAlongSketchNormal() const;
bool getCustom() const;
std::string getReferenceAxis() const;
@@ -127,42 +196,52 @@ protected:
double getYDirection() const;
double getZDirection() const;
bool getReversed() const;
bool getMidplane() const;
int getMode() const;
QString getFaceName() const;
int getMode2() const;
int getSidesMode() const;
QString getFaceName(QLineEdit*) const;
void onSelectionChanged(const Gui::SelectionChanges& msg) override;
virtual void translateModeList(int index);
virtual void updateUI(int index);
void translateSidesList(int index);
virtual void translateModeList(QComboBox* box, int index);
virtual void updateUI(Side side);
void updateDirectionEdits();
void setDirectionMode(int index);
void handleLineFaceNameClick();
void handleLineFaceNameNo();
void handleLineFaceNameClick(QLineEdit*);
void handleLineFaceNameNo(QLineEdit*);
private:
void setupSideDialog(SideController& side);
void selectedReferenceAxis(const Gui::SelectionChanges& msg);
void selectedFace(const Gui::SelectionChanges& msg);
void selectedShape(const Gui::SelectionChanges& msg);
void selectedShapeFace(const Gui::SelectionChanges& msg);
void selectedFace(const Gui::SelectionChanges& msg, SideController& side);
void selectedShape(const Gui::SelectionChanges& msg, SideController& side);
void selectedShapeFace(const Gui::SelectionChanges& msg, SideController& side);
void tryRecomputeFeature();
void translateFaceName();
void translateFaceName(QLineEdit*);
void connectSlots();
bool hasProfileFace(PartDesign::ProfileBased*) const;
void clearFaceName();
void clearFaceName(QLineEdit*);
void updateShapeName();
void updateShapeFaces();
void updateShapeName(QLineEdit*, App::PropertyLinkSubList&);
void updateShapeFaces(QListWidget* list, App::PropertyLinkSubList& prop);
std::vector<std::string> getShapeFaces();
std::vector<std::string> getShapeFaces(App::PropertyLinkSubList& prop);
void changeFaceName(QLineEdit* lineEdit, const QString& text);
void createSideControllers();
protected:
QWidget* proxy;
QAction* unselectShapeFaceAction;
QAction* unselectShapeFaceAction2;
std::unique_ptr<Ui_TaskPadPocketParameters> ui;
std::vector<std::unique_ptr<App::PropertyLinkSub>> axesInList;
SelectionMode selectionMode = None;
Side activeSelectionSide = Side::First;
};
class TaskDlgExtrudeParameters : public TaskDlgSketchBasedParameters

View File

@@ -39,7 +39,8 @@ using namespace Gui;
TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, bool newObj)
: TaskExtrudeParameters(PadView, parent, "PartDesign_Pad", tr("Pad Parameters"))
{
ui->offsetEdit->setToolTip(tr("Offsets the pad from the face at which the pad will end"));
ui->offsetEdit->setToolTip(tr("Offset the pad from the face at which the pad will end on side 1"));
ui->offsetEdit2->setToolTip(tr("Offset the pad from the face at which the pad will end on side 2"));
ui->checkBoxReversed->setToolTip(tr("Reverses pad direction"));
// set the history path
@@ -49,6 +50,8 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent,
ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadLength2"));
ui->offsetEdit->setEntryName(QByteArray("Offset"));
ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadOffset"));
ui->offsetEdit2->setEntryName(QByteArray("Offset2"));
ui->offsetEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadOffset2"));
ui->taperEdit->setEntryName(QByteArray("TaperAngle"));
ui->taperEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadTaperAngle"));
ui->taperEdit2->setEntryName(QByteArray("TaperAngle2"));
@@ -64,71 +67,70 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent,
TaskPadParameters::~TaskPadParameters() = default;
void TaskPadParameters::translateModeList(int index)
void TaskPadParameters::translateModeList(QComboBox* box, int index)
{
ui->changeMode->clear();
ui->changeMode->addItem(tr("Dimension"));
ui->changeMode->addItem(tr("To last"));
ui->changeMode->addItem(tr("To first"));
ui->changeMode->addItem(tr("Up to face"));
ui->changeMode->addItem(tr("Two dimensions"));
ui->changeMode->addItem(tr("Up to shape"));
ui->changeMode->setCurrentIndex(index);
box->clear();
box->addItem(tr("Dimension"));
box->addItem(tr("To last"));
box->addItem(tr("To first"));
box->addItem(tr("Up to face"));
box->addItem(tr("Up to shape"));
box->setCurrentIndex(index);
}
void TaskPadParameters::updateUI(int index)
void TaskPadParameters::updateUI(Side side)
{
// update direction combobox
fillDirectionCombo();
// set and enable checkboxes
setCheckboxes(static_cast<Mode>(index), Type::Pad);
updateWholeUI(Type::Pad, side);
}
void TaskPadParameters::onModeChanged(int index)
void TaskPadParameters::onModeChanged(int index, Side side)
{
auto pcPad = getObject<PartDesign::Pad>();
auto& sideCtrl = getSideController(side);
switch (static_cast<Mode>(index)) {
case Mode::Dimension:
pcPad->Type.setValue("Length");
// Avoid error message
if (ui->lengthEdit->value() < Base::Quantity(Precision::Confusion(), Base::Unit::Length))
ui->lengthEdit->setValue(5.0);
break;
case Mode::ToLast:
pcPad->Type.setValue("UpToLast");
break;
case Mode::ToFirst:
pcPad->Type.setValue("UpToFirst");
break;
case Mode::ToFace:
// Note: ui->checkBoxReversed is purposely enabled because the selected face
// could be a circular one around the sketch
pcPad->Type.setValue("UpToFace");
if (ui->lineFaceName->text().isEmpty()) {
ui->buttonFace->setChecked(true);
handleLineFaceNameClick(); // sets placeholder text
}
break;
case Mode::TwoDimensions:
pcPad->Type.setValue("TwoLengths");
break;
case Mode::ToShape:
pcPad->Type.setValue("UpToShape");
break;
case Mode::Dimension:
sideCtrl.Type->setValue("Length");
if (side == Side::First) {
// Avoid error message
double L = sideCtrl.lengthEdit->value().getValue();
Side otherSide = side == Side::First ? Side::Second : Side::First;
auto& sideCtrl2 = getSideController(otherSide);
double L2 = static_cast<SidesMode>(getSidesMode()) == SidesMode::TwoSides
? sideCtrl2.lengthEdit->value().getValue()
: 0;
if (std::abs(L + L2) < Precision::Confusion()) {
sideCtrl.lengthEdit->setValue(5.0);
}
}
break;
case Mode::ToLast:
sideCtrl.Type->setValue("UpToLast");
break;
case Mode::ToFirst:
sideCtrl.Type->setValue("UpToFirst");
break;
case Mode::ToFace:
sideCtrl.Type->setValue("UpToFace");
if (sideCtrl.lineFaceName->text().isEmpty()) {
sideCtrl.buttonFace->setChecked(true);
handleLineFaceNameClick(sideCtrl.lineFaceName); // sets placeholder text
}
break;
case Mode::ToShape:
sideCtrl.Type->setValue("UpToShape");
break;
}
updateUI(index);
updateUI(side);
recomputeFeature();
}
void TaskPadParameters::apply()
{
QString facename = QStringLiteral("None");
if (static_cast<Mode>(getMode()) == Mode::ToFace) {
facename = getFaceName();
}
applyParameters(facename);
applyParameters();
}
//**************************************************************************

View File

@@ -26,6 +26,7 @@
#include "TaskExtrudeParameters.h"
#include "ViewProviderPad.h"
class QComboBox;
namespace App {
class Property;
@@ -49,9 +50,9 @@ public:
void apply() override;
private:
void onModeChanged(int index) override;
void translateModeList(int index) override;
void updateUI(int index) override;
void onModeChanged(int index, Side side) override;
void translateModeList(QComboBox* box, int index) override;
void updateUI(Side side) override;
};
/// simulation dialog for the TaskView

View File

@@ -17,13 +17,59 @@
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="sidesLabel">
<property name="text">
<string>Mode</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="sidesMode"/>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="verticalLayout_22">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="side1Label">
<property name="text">
<string>Side 1</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="textLabel1">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item row="2" column="1">
<widget class="QComboBox" name="changeMode">
<item>
<property name="text">
@@ -32,14 +78,14 @@
</item>
</widget>
</item>
<item row="1" column="0">
<item row="3" column="0">
<widget class="QLabel" name="labelLength">
<property name="text">
<string>Length</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="3" column="1">
<widget class="Gui::PrefQuantitySpinBox" name="lengthEdit" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
@@ -47,36 +93,16 @@
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<property name="minimum" stdset="0">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelLength2">
<property name="text">
<string>2nd length</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::PrefQuantitySpinBox" name="lengthEdit2" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="labelOffset">
<property name="text">
<string>Offset to face</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="Gui::PrefQuantitySpinBox" name="offsetEdit" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
@@ -86,147 +112,390 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="upToShapeList">
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QVBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="_2">
<item row="0" column="1">
<widget class="QLineEdit" name="lineShapeName">
<property name="readOnly">
<item row="5" column="0">
<widget class="QLabel" name="labelTaperAngle">
<property name="toolTip">
<string>Angle to taper the extrusion</string>
</property>
<property name="text">
<string>Taper angle</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="Gui::PrefQuantitySpinBox" name="taperEdit" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QFrame" name="upToShapeList">
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QVBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="_2">
<item row="0" column="1">
<widget class="QLineEdit" name="lineShapeName">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="buttonShape">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Select Shape</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBoxAllFaces">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Selects all faces of the shape</string>
</property>
<property name="text">
<string>Select all faces</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="buttonShape">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Select shape</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<item>
<widget class="QWidget" name="upToShapeFaces" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="buttonShapeFace">
<property name="toolTip">
<string>Toggles between selection and preview mode</string>
</property>
<property name="text">
<string>Select</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listWidgetReferences">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBoxAllFaces">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Applies length symmetrically to sketch plane</string>
</property>
<property name="text">
<string>Select all faces</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="upToShapeFaces" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1">
<widget class="QLineEdit" name="lineFaceName">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="topMargin">
<number>0</number>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="buttonFace">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="rightMargin">
<number>0</number>
<property name="text">
<string>Select Face</string>
</property>
<property name="bottomMargin">
<number>0</number>
<property name="checkable">
<bool>true</bool>
</property>
<item>
<widget class="QToolButton" name="buttonShapeFace">
<property name="toolTip">
<string>Toggles between selection and preview mode</string>
</property>
<property name="text">
<string>Select</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listWidgetReferences">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1">
<widget class="QLineEdit" name="lineFaceName">
<property name="readOnly">
<bool>true</bool>
</widget>
</item>
</layout>
</item>
<item row="8" column="0" colspan="2">
<layout class="QHBoxLayout" name="verticalLayout_22">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="side2Label">
<property name="text">
<string>Side 2</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="9" column="0">
<widget class="QLabel" name="typeLabel2">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="buttonFace">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<item row="9" column="1">
<widget class="QComboBox" name="changeMode2"/>
</item>
<item row="10" column="0">
<widget class="QLabel" name="labelLength2">
<property name="text">
<string>Select face</string>
</property>
<property name="checkable">
<bool>true</bool>
<string>Length</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="Gui::PrefQuantitySpinBox" name="lengthEdit2" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="labelOffset2">
<property name="text">
<string>Offset to face</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="Gui::PrefQuantitySpinBox" name="offsetEdit2" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="labelTaperAngle2">
<property name="toolTip">
<string>Angle to taper the extrusion</string>
</property>
<property name="text">
<string>Taper angle</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="Gui::PrefQuantitySpinBox" name="taperEdit2" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
</widget>
</item>
<item row="13" column="0" colspan="2">
<widget class="QFrame" name="upToShapeList2">
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QVBoxLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="_22">
<item row="0" column="1">
<widget class="QLineEdit" name="lineShapeName2">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="buttonShape2">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Select Shape</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBoxAllFaces2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Selects all faces of the shape</string>
</property>
<property name="text">
<string>Select all faces</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="upToShapeFaces2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_22">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="buttonShapeFace2">
<property name="toolTip">
<string>Toggles between selection and preview mode</string>
</property>
<property name="text">
<string>Select</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listWidgetReferences2">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="14" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_52">
<item row="0" column="1">
<widget class="QLineEdit" name="lineFaceName2">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="buttonFace2">
<property name="minimumSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Select Face</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBoxMidplane">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Applies length symmetrically to sketch plane</string>
</property>
<property name="text">
<string>Symmetric to plane</string>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
@@ -410,61 +679,6 @@ measured along the specified direction</string>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="labelTaperAngle">
<property name="toolTip">
<string>Angle to taper the extrusion</string>
</property>
<property name="text">
<string>Taper angle</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefQuantitySpinBox" name="taperEdit" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="labelTaperAngle2">
<property name="toolTip">
<string>Angle to taper the extrusion</string>
</property>
<property name="text">
<string>2nd taper angle</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefQuantitySpinBox" name="taperEdit2" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxUpdateView">
<property name="text">
@@ -494,25 +708,6 @@ measured along the specified direction</string>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>changeMode</tabstop>
<tabstop>lengthEdit</tabstop>
<tabstop>lengthEdit2</tabstop>
<tabstop>offsetEdit</tabstop>
<tabstop>buttonFace</tabstop>
<tabstop>lineFaceName</tabstop>
<tabstop>checkBoxMidplane</tabstop>
<tabstop>checkBoxReversed</tabstop>
<tabstop>directionCB</tabstop>
<tabstop>checkBoxDirection</tabstop>
<tabstop>XDirectionEdit</tabstop>
<tabstop>YDirectionEdit</tabstop>
<tabstop>ZDirectionEdit</tabstop>
<tabstop>checkBoxAlongDirection</tabstop>
<tabstop>taperEdit</tabstop>
<tabstop>taperEdit2</tabstop>
<tabstop>checkBoxUpdateView</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -38,9 +38,9 @@ using namespace Gui;
TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidget *parent, bool newObj)
: TaskExtrudeParameters(PocketView, parent, "PartDesign_Pocket", tr("Pocket Parameters"))
, oldLength(0)
{
ui->offsetEdit->setToolTip(tr("Offset from face at which pocket will end"));
ui->offsetEdit->setToolTip(tr("Offset from the selected face at which the pocket will end on side 1"));
ui->offsetEdit2->setToolTip(tr("Offset from the selected face at which the pocket will end on side 2"));
ui->checkBoxReversed->setToolTip(tr("Reverses pocket direction"));
// set the history path
@@ -50,6 +50,8 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge
ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketLength2"));
ui->offsetEdit->setEntryName(QByteArray("Offset"));
ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketOffset"));
ui->offsetEdit2->setEntryName(QByteArray("Offset2"));
ui->offsetEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketOffset2"));
ui->taperEdit->setEntryName(QByteArray("TaperAngle"));
ui->taperEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketTaperAngle"));
ui->taperEdit2->setEntryName(QByteArray("TaperAngle2"));
@@ -65,81 +67,74 @@ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidge
TaskPocketParameters::~TaskPocketParameters() = default;
void TaskPocketParameters::translateModeList(int index)
void TaskPocketParameters::translateModeList(QComboBox* box, int index)
{
ui->changeMode->clear();
ui->changeMode->addItem(tr("Dimension"));
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"));
ui->changeMode->addItem(tr("Up to shape"));
ui->changeMode->setCurrentIndex(index);
box->clear();
box->addItem(tr("Dimension"));
box->addItem(tr("Through all"));
box->addItem(tr("To first"));
box->addItem(tr("Up to face"));
box->addItem(tr("Up to shape"));
box->setCurrentIndex(index);
}
void TaskPocketParameters::updateUI(int index)
void TaskPocketParameters::updateUI(Side side)
{
// update direction combobox
fillDirectionCombo();
// set and enable checkboxes
setCheckboxes(static_cast<Mode>(index), Type::Pocket);
updateWholeUI(Type::Pocket, side);
}
void TaskPocketParameters::onModeChanged(int index)
void TaskPocketParameters::onModeChanged(int index, Side side)
{
auto pcPocket = getObject<PartDesign::Pocket>();
auto& sideCtrl = getSideController(side);
switch (static_cast<Mode>(index)) {
case Mode::Dimension:
// Why? See below for "UpToFace"
if (oldLength < Precision::Confusion())
oldLength = 5.0;
pcPocket->Length.setValue(oldLength);
ui->lengthEdit->setValue(oldLength);
pcPocket->Type.setValue("Length");
sideCtrl.Type->setValue("Length");
if (side == Side::First) {
// Avoid error message
double L = sideCtrl.lengthEdit->value().getValue();
Side otherSide = side == Side::First ? Side::Second : Side::First;
auto& sideCtrl2 = getSideController(otherSide);
double L2 = static_cast<SidesMode>(getSidesMode()) == SidesMode::TwoSides
? sideCtrl2.lengthEdit->value().getValue()
: 0;
if (std::abs(L + L2) < Precision::Confusion()) {
sideCtrl.lengthEdit->setValue(5.0);
}
}
break;
case Mode::ThroughAll:
oldLength = pcPocket->Length.getValue();
pcPocket->Type.setValue("ThroughAll");
sideCtrl.Type->setValue("ThroughAll");
break;
case Mode::ToFirst:
oldLength = pcPocket->Length.getValue();
pcPocket->Type.setValue("UpToFirst");
sideCtrl.Type->setValue("UpToFirst");
break;
case Mode::ToFace:
// Note: ui->checkBoxReversed is purposely enabled because the selected face
// could be a circular one around the sketch
// Also note: Because of the code at the beginning of Pocket::execute() which is used
// to detect broken legacy parts, we must set the length to zero here!
oldLength = pcPocket->Length.getValue();
pcPocket->Type.setValue("UpToFace");
pcPocket->Length.setValue(0.0);
ui->lengthEdit->setValue(0.0);
if (ui->lineFaceName->text().isEmpty()) {
ui->buttonFace->setChecked(true);
handleLineFaceNameClick(); // sets placeholder text
sideCtrl.Type->setValue("UpToFace");
if (sideCtrl.lineFaceName->text().isEmpty()) {
sideCtrl.buttonFace->setChecked(true);
handleLineFaceNameClick(sideCtrl.lineFaceName); // sets placeholder text
}
break;
case Mode::TwoDimensions:
oldLength = pcPocket->Length.getValue();
pcPocket->Type.setValue("TwoLengths");
break;
case Mode::ToShape:
pcPocket->Type.setValue("UpToShape");
sideCtrl.Type->setValue("UpToShape");
break;
}
updateUI(index);
updateUI(side);
recomputeFeature();
}
void TaskPocketParameters::apply()
{
QString facename = QStringLiteral("None");
if (static_cast<Mode>(getMode()) == Mode::ToFace) {
facename = getFaceName();
}
applyParameters(facename);
applyParameters();
}
//**************************************************************************

View File

@@ -26,6 +26,7 @@
#include "TaskExtrudeParameters.h"
#include "ViewProviderPocket.h"
class QComboBox;
namespace App {
class Property;
@@ -49,12 +50,9 @@ public:
void apply() override;
private:
void onModeChanged(int index) override;
void translateModeList(int index) override;
void updateUI(int index) override;
private:
double oldLength;
void onModeChanged(int index, Side side) override;
void translateModeList(QComboBox* box, int index) override;
void updateUI(Side side) override;
};
/// simulation dialog for the TaskView

View File

@@ -385,7 +385,8 @@ void TaskRevolutionParameters::onSelectionChanged(const Gui::SelectionChanges& m
if (msg.Type == Gui::SelectionChanges::AddSelection) {
int mode = ui->changeMode->currentIndex();
if (selectionFace) {
QString refText = onAddSelection(msg);
auto rev = getObject<PartDesign::Revolution>();
QString refText = onAddSelection(msg, rev->UpToFace);
if (refText.length() > 0) {
QSignalBlocker block(ui->lineFaceName);
ui->lineFaceName->setText(refText);

View File

@@ -58,7 +58,7 @@ TaskSketchBasedParameters::TaskSketchBasedParameters(PartDesignGui::ViewProvider
this->blockSelection(true);
}
const QString TaskSketchBasedParameters::onAddSelection(const Gui::SelectionChanges& msg)
const QString TaskSketchBasedParameters::onAddSelection(const Gui::SelectionChanges& msg, App::PropertyLinkSub& prop)
{
// Note: The validity checking has already been done in ReferenceSelection.cpp
auto sketchBased = getObject<PartDesign::ProfileBased>();
@@ -81,7 +81,7 @@ const QString TaskSketchBasedParameters::onAddSelection(const Gui::SelectionChan
}
std::vector<std::string> upToFaces(1,subname);
sketchBased->UpToFace.setValue(selObj, upToFaces);
prop.setValue(selObj, upToFaces);
recomputeFeature();
return refStr;

View File

@@ -33,6 +33,7 @@
namespace App {
class Property;
class PropertyLinkSubList;
}
namespace PartDesignGui {
@@ -51,7 +52,7 @@ public:
protected:
void onSelectionChanged(const Gui::SelectionChanges& msg) override =0;
const QString onAddSelection(const Gui::SelectionChanges& msg);
const QString onAddSelection(const Gui::SelectionChanges& msg, App::PropertyLinkSub& prop);
virtual void startReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base);
virtual void finishReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base);
/*!

View File

@@ -178,7 +178,7 @@ class TestPad(unittest.TestCase):
self.Pad1 = self.Doc.addObject("PartDesign::Pad", "Pad1")
self.Body.addObject(self.Pad1)
self.Pad1.Profile = self.PadSketch1
self.Pad1.Type = 4
self.Pad1.SideType = 1
self.Pad1.Length = 1.0
self.Pad1.Length2 = 2.0
self.Pad1.Reversed = 1
@@ -238,7 +238,7 @@ class TestPad(unittest.TestCase):
self.Pad1 = self.Doc.addObject("PartDesign::Pad", "Pad1")
self.Body.addObject(self.Pad1)
self.Pad1.Profile = self.PadSketch1
self.Pad1.Type = 5
self.Pad1.Type = 4
self.Doc.recompute()
self.assertAlmostEqual(self.Pad1.Shape.Volume, 2.58787, places=4)

View File

@@ -76,7 +76,7 @@ TEST_F(PadTest, TestMidPlaneTwoLength)
pad->Length.setValue(10.0);
pad->Length2.setValue(20.0);
pad->Type.setValue("TwoLengths");
pad->SideType.setValue("Two sides");
doc->recompute();