diff --git a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp
index 9663487158..2f2b4a9504 100644
--- a/src/Mod/Part/Gui/DlgSettingsGeneral.cpp
+++ b/src/Mod/Part/Gui/DlgSettingsGeneral.cpp
@@ -62,6 +62,7 @@ void DlgSettingsGeneral::saveSettings()
ui->checkSketchBaseRefine->onSave();
ui->checkObjectNaming->onSave();
ui->checkAllowCompoundBody->onSave();
+ ui->comboDefaultProfileTypeForHole->onSave();
}
void DlgSettingsGeneral::loadSettings()
@@ -71,6 +72,7 @@ void DlgSettingsGeneral::loadSettings()
ui->checkSketchBaseRefine->onRestore();
ui->checkObjectNaming->onRestore();
ui->checkAllowCompoundBody->onRestore();
+ ui->comboDefaultProfileTypeForHole->onRestore();
}
/**
diff --git a/src/Mod/Part/Gui/DlgSettingsGeneral.ui b/src/Mod/Part/Gui/DlgSettingsGeneral.ui
index c764b3d896..e06db48c68 100644
--- a/src/Mod/Part/Gui/DlgSettingsGeneral.ui
+++ b/src/Mod/Part/Gui/DlgSettingsGeneral.ui
@@ -7,7 +7,7 @@
0
0
550
- 333
+ 450
@@ -21,11 +21,11 @@
-
-
-
+
+
Automatically check model after boolean operation
-
+
true
@@ -37,11 +37,11 @@
-
-
-
+
+
Automatically refine model after boolean operation
-
+
true
@@ -53,11 +53,11 @@
-
-
-
+
+
Automatically refine model after sketch-based operation
-
+
true
@@ -84,8 +84,8 @@
-
-
-
+
+
Add name of base object
@@ -99,6 +99,56 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Features settings
+
+
+
-
+
+
+ Default profile type for holes
+
+
+
+ -
+
+
+ 1
+
+
+ defaultBaseTypeHole
+
+
+ Mod/PartDesign
+
+
-
+
+ Circles and arcs
+
+
+ -
+
+ Points, circles and arcs
+
+
+ -
+
+ Points
+
+
+
+
+
+
+
-
@@ -154,13 +204,13 @@
- Gui::PrefCheckBox
- QCheckBox
+ Gui::PrefComboBox
+ QComboBox
- Gui::PrefDoubleSpinBox
- QDoubleSpinBox
+ Gui::PrefCheckBox
+ QWidget
diff --git a/src/Mod/PartDesign/App/FeatureHole.cpp b/src/Mod/PartDesign/App/FeatureHole.cpp
index 47c69790c3..a68da0fec8 100644
--- a/src/Mod/PartDesign/App/FeatureHole.cpp
+++ b/src/Mod/PartDesign/App/FeatureHole.cpp
@@ -24,6 +24,7 @@
#include "PreCompiled.h"
#ifndef _PreComp_
# include
+# include
# include
# include
# include
@@ -37,6 +38,7 @@
# include
# include
# include
+# include
# include
# include
# include
@@ -60,6 +62,8 @@
#include "FeatureHole.h"
#include "json.hpp"
+#include
+
FC_LOG_LEVEL_INIT("PartDesign", true, true);
namespace PartDesign {
@@ -813,6 +817,9 @@ Hole::Hole()
ADD_PROPERTY_TYPE(CustomThreadClearance, (0.0), "Hole", App::Prop_None, "Custom thread clearance (overrides ThreadClass)");
+ // Defaults to circles & arcs so that older files are kept intact
+ // while new file get points, circles and arcs set in setupObject()
+ ADD_PROPERTY_TYPE(BaseProfileType, (BaseProfileTypeOptions::OnCirclesArcs), "Hole", App::Prop_None, "Which profile feature to base the holes on");
}
void Hole::updateHoleCutParams()
@@ -1676,6 +1683,19 @@ void Hole::onChanged(const App::Property* prop)
ProfileBased::onChanged(prop);
}
+void Hole::setupObject()
+{
+ // Set the BaseProfileType to the user defined value
+ // here so that new objects use points, but older files
+ // keep the default value of "Circles and Arcs"
+
+ Base::Reference hGrp = App::GetApplication().GetUserParameter()
+ .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/PartDesign");
+
+ BaseProfileType.setValue(baseProfileOption_idxToBitmask(hGrp->GetInt("defaultBaseTypeHole", 1)));
+
+ ProfileBased::setupObject();
+}
/**
* Computes 2D intersection between the lines (pa1, pa2) and (pb1, pb2).
@@ -1752,7 +1772,8 @@ short Hole::mustExecute() const
UseCustomThreadClearance.isTouched() ||
CustomThreadClearance.isTouched() ||
ThreadDepthType.isTouched() ||
- ThreadDepth.isTouched()
+ ThreadDepth.isTouched() ||
+ BaseProfileType.isTouched()
)
return 1;
return ProfileBased::mustExecute();
@@ -1788,6 +1809,7 @@ void Hole::updateProps()
onChanged(&CustomThreadClearance);
onChanged(&ThreadDepthType);
onChanged(&ThreadDepth);
+ onChanged(&BaseProfileType);
}
static gp_Pnt toPnt(gp_Vec dir)
@@ -1796,14 +1818,8 @@ static gp_Pnt toPnt(gp_Vec dir)
}
App::DocumentObjectExecReturn* Hole::execute()
-{
- TopoShape profileshape;
- try {
- profileshape = getTopoShapeVerifiedFace();
- }
- catch (const Base::Exception& e) {
- return new App::DocumentObjectExecReturn(e.what());
- }
+{
+ TopoShape profileshape = getProfileShape(/*needSubElement*/ false);
// Find the base shape
TopoShape base;
@@ -1826,10 +1842,6 @@ App::DocumentObjectExecReturn* Hole::execute()
TopLoc_Location invObjLoc = this->getLocation().Inverted();
base.move(invObjLoc);
-
- if (profileshape.isNull())
- return new App::DocumentObjectExecReturn(
- QT_TRANSLATE_NOOP("Exception", "Hole error: Creating a face from sketch failed"));
profileshape.move(invObjLoc);
/* Build the prototype hole */
@@ -2177,33 +2189,59 @@ TopoShape Hole::findHoles(std::vector &holes,
{
TopoShape result(0);
- for(const auto &profileEdge : profileshape.getSubTopoShapes(TopAbs_EDGE)) {
- Standard_Real c_start;
- Standard_Real c_end;
- TopoDS_Edge edge = TopoDS::Edge(profileEdge.getShape());
- Handle(Geom_Curve) c = BRep_Tool::Curve(edge, c_start, c_end);
-
- // Circle?
- if (c->DynamicType() != STANDARD_TYPE(Geom_Circle))
- continue;
-
- Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(c);
- gp_Pnt loc = circle->Axis().Location();
-
-
+ auto addHole = [&](Part::TopoShape const& baseshape, gp_Pnt loc) {
gp_Trsf localSketchTransformation;
localSketchTransformation.SetTranslation( gp_Pnt( 0, 0, 0 ),
gp_Pnt(loc.X(), loc.Y(), loc.Z()) );
Part::ShapeMapper mapper;
- mapper.populate(Part::MappingStatus::Modified, profileEdge, TopoShape(protoHole).getSubTopoShapes(TopAbs_FACE));
+ mapper.populate(Part::MappingStatus::Modified, baseshape, TopoShape(protoHole).getSubTopoShapes(TopAbs_FACE));
TopoShape hole(-getID());
- hole.makeShapeWithElementMap(protoHole, mapper, {profileEdge});
+ hole.makeShapeWithElementMap(protoHole, mapper, {baseshape});
// transform and generate element map.
hole = hole.makeElementTransform(localSketchTransformation);
holes.push_back(hole);
+ };
+
+ int baseProfileType = BaseProfileType.getValue();
+
+ // Iterate over edges and filter out non-circle/non-arc types
+ if (baseProfileType & BaseProfileTypeOptions::OnCircles ||
+ baseProfileType & BaseProfileTypeOptions::OnArcs) {
+ for (const auto &profileEdge : profileshape.getSubTopoShapes(TopAbs_EDGE)) {
+ TopoDS_Edge edge = TopoDS::Edge(profileEdge.getShape());
+ BRepAdaptor_Curve adaptor(edge);
+
+ // Circle base?
+ if (adaptor.GetType() != GeomAbs_Circle) {
+ continue;
+ }
+ // Filter for circles
+ if (!(baseProfileType & BaseProfileTypeOptions::OnCircles) && adaptor.IsClosed()) {
+ continue;
+ }
+
+ // Filter for arcs
+ if (!(baseProfileType & BaseProfileTypeOptions::OnArcs) && !adaptor.IsClosed()) {
+ continue;
+ }
+
+ gp_Circ circle = adaptor.Circle();
+ addHole(profileEdge, circle.Axis().Location());
+ }
+ }
+
+ // To avoid breaking older files which where not made with
+ // holes on points
+ if (baseProfileType & BaseProfileTypeOptions::OnPoints) {
+ // Iterate over vertices while avoiding edges so that curve handles are ignored
+ for (const auto &profileVertex : profileshape.getSubTopoShapes(TopAbs_VERTEX, TopAbs_EDGE)) {
+ TopoDS_Vertex vertex = TopoDS::Vertex(profileVertex.getShape());
+
+ addHole(profileVertex, BRep_Tool::Pnt(vertex));
+ }
}
return TopoShape().makeElementCompound(holes);
}
@@ -2577,4 +2615,37 @@ void Hole::readCutDefinitions()
}
}
+int Hole::baseProfileOption_idxToBitmask(int index)
+{
+ // Translate combobox index to bitmask value
+ // More options could be made available
+ if (index == 0) {
+ return PartDesign::Hole::BaseProfileTypeOptions::OnCirclesArcs;
+ }
+ if (index == 1) {
+ return PartDesign::Hole::BaseProfileTypeOptions::OnPointsCirclesArcs;
+ }
+ if (index == 2) {
+ return PartDesign::Hole::BaseProfileTypeOptions::OnPoints;
+ }
+ Base::Console().Error("Unexpected hole base profile combobox index: %i", index);
+ return 0;
+}
+int Hole::baseProfileOption_bitmaskToIdx(int bitmask)
+{
+ if (bitmask == PartDesign::Hole::BaseProfileTypeOptions::OnCirclesArcs) {
+ return 0;
+ }
+ if (bitmask == PartDesign::Hole::BaseProfileTypeOptions::OnPointsCirclesArcs) {
+ return 1;
+ }
+ if (bitmask == PartDesign::Hole::BaseProfileTypeOptions::OnPoints) {
+ return 2;
+ }
+
+ Base::Console().Error("Unexpected hole base profile bitmask: %i", bitmask);
+ return -1;
+}
+
+
} // namespace PartDesign
diff --git a/src/Mod/PartDesign/App/FeatureHole.h b/src/Mod/PartDesign/App/FeatureHole.h
index 484bbbab54..956da4411b 100644
--- a/src/Mod/PartDesign/App/FeatureHole.h
+++ b/src/Mod/PartDesign/App/FeatureHole.h
@@ -74,6 +74,19 @@ public:
App::PropertyAngle TaperedAngle;
App::PropertyBool UseCustomThreadClearance;
App::PropertyLength CustomThreadClearance;
+ App::PropertyInteger BaseProfileType;
+
+ enum BaseProfileTypeOptions {
+ OnPoints = 1 << 0,
+ OnCircles = 1 << 1,
+ OnArcs = 1 << 2,
+
+ // Common combos
+ OnPointsCirclesArcs = OnPoints | OnCircles | OnArcs,
+ OnCirclesArcs = OnCircles | OnArcs
+ };
+ static int baseProfileOption_idxToBitmask(int index);
+ static int baseProfileOption_bitmaskToIdx(int bitmask);
/** @name methods override feature */
//@{
@@ -113,6 +126,8 @@ public:
protected:
void onChanged(const App::Property* prop) override;
+ void setupObject() override;
+
static const App::PropertyAngle::Constraints floatAngle;
private:
diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp
index 3fc8a2f186..8c285edf29 100644
--- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp
+++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp
@@ -337,7 +337,7 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
}
else if (AllowMultiFace.getValue()) {
try {
- auto shape = getProfileShape();
+ auto shape = getProfileShape(/*needSubElement*/ false);
if (shape.isNull())
err = "Linked shape object is empty";
else {
@@ -404,7 +404,7 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const {
return TopoDS_Face();
}
-TopoShape ProfileBased::getProfileShape() const
+TopoShape ProfileBased::getProfileShape(bool needSubElement) const
{
TopoShape shape;
const auto& subs = Profile.getSubValues();
@@ -416,7 +416,7 @@ TopoShape ProfileBased::getProfileShape() const
std::vector shapes;
for (auto& sub : subs) {
shapes.push_back(
- Part::Feature::getTopoShape(profile, sub.c_str(), /* needSubElement */ true));
+ Part::Feature::getTopoShape(profile, sub.c_str(), needSubElement));
}
shape = TopoShape(shape.Tag).makeElementCompound(shapes);
}
@@ -472,7 +472,7 @@ std::vector ProfileBased::getTopoShapeProfileWires() const
// tessellations for some faces. Making an explicit copy of the linked
// shape seems to fix it. The error mostly happens when re-computing the
// shape but sometimes also for the first time
- auto shape = getProfileShape().makeElementCopy();
+ auto shape = getProfileShape(/*needSubElement*/ false).makeElementCopy();
if (shape.hasSubShape(TopAbs_WIRE)) {
return shape.getSubTopoShapes(TopAbs_WIRE);
@@ -944,11 +944,14 @@ double ProfileBased::getThroughAllLength() const
{
TopoShape profileshape;
TopoShape base;
- profileshape = getTopoShapeVerifiedFace();
+ profileshape = getTopoShapeVerifiedFace(true);
base = getBaseTopoShape();
Bnd_Box box;
BRepBndLib::Add(base.getShape(), box);
- BRepBndLib::Add(profileshape.getShape(), box);
+
+ if (!profileshape.isNull()) {
+ BRepBndLib::Add(profileshape.getShape(), box);
+ }
box.SetGap(0.0);
// The diagonal of the bounding box, plus 1% extra to eliminate risk of
// co-planar issues, gives a length that is guaranteed to go through all.
diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h
index 08be8a10ef..dc6cc81eed 100644
--- a/src/Mod/PartDesign/App/FeatureSketchBased.h
+++ b/src/Mod/PartDesign/App/FeatureSketchBased.h
@@ -131,7 +131,7 @@ public:
virtual Base::Vector3d getProfileNormal() const;
- TopoShape getProfileShape() const;
+ TopoShape getProfileShape(bool needSubElement = false) const;
/// retrieves the number of axes in the linked sketch (defined as construction lines)
int getSketchAxisCount() const;
diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp
index e9de2e21ba..a29aea8ac1 100644
--- a/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp
+++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.cpp
@@ -183,6 +183,8 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole* HoleView, QWidget* pare
std::string(pcHole->ThreadDepthType.getValueAsString()) == "Dimension"
);
+ ui->BaseProfileType->setCurrentIndex(PartDesign::Hole::baseProfileOption_bitmaskToIdx(pcHole->BaseProfileType.getValue()));
+
setCutDiagram();
// clang-format off
@@ -240,6 +242,8 @@ TaskHoleParameters::TaskHoleParameters(ViewProviderHole* HoleView, QWidget* pare
this, &TaskHoleParameters::threadDepthTypeChanged);
connect(ui->ThreadDepth, qOverload(&Gui::QuantitySpinBox::valueChanged),
this, &TaskHoleParameters::threadDepthChanged);
+ connect(ui->BaseProfileType, qOverload(&QComboBox::currentIndexChanged),
+ this, &TaskHoleParameters::baseProfileTypeChanged);
// clang-format on
getViewObject()->show();
@@ -321,7 +325,6 @@ void TaskHoleParameters::threadDepthTypeChanged(int index)
recomputeFeature();
}
}
-
void TaskHoleParameters::threadDepthChanged(double value)
{
if (auto hole = getObject()) {
@@ -410,6 +413,13 @@ void TaskHoleParameters::holeCutTypeChanged(int index)
}
setCutDiagram();
}
+void TaskHoleParameters::baseProfileTypeChanged(int index)
+{
+ if (auto hole = getObject()) {
+ hole->BaseProfileType.setValue(PartDesign::Hole::baseProfileOption_idxToBitmask(index));
+ recomputeFeature();
+ }
+}
void TaskHoleParameters::setCutDiagram()
{
@@ -907,6 +917,9 @@ void TaskHoleParameters::changedObject(const App::Document&, const App::Property
else if (&Prop == &hole->ThreadDepth) {
ui->ThreadDepth->setEnabled(true);
updateSpinBox(ui->ThreadDepth, hole->ThreadDepth.getValue());
+ } else if (&Prop == &hole->BaseProfileType) {
+ ui->BaseProfileType->setEnabled(true);
+ updateComboBox(ui->BaseProfileType, PartDesign::Hole::baseProfileOption_bitmaskToIdx(hole->BaseProfileType.getValue()));
}
}
@@ -1056,7 +1069,10 @@ double TaskHoleParameters::getThreadDepth() const
{
return ui->ThreadDepth->value().getValue();
}
-
+int TaskHoleParameters::getBaseProfileType() const
+{
+ return PartDesign::Hole::baseProfileOption_idxToBitmask(ui->BaseProfileType->currentIndex());
+}
void TaskHoleParameters::apply()
{
auto hole = getObject();
@@ -1123,6 +1139,9 @@ void TaskHoleParameters::apply()
if (!hole->Tapered.isReadOnly()) {
FCMD_OBJ_CMD(hole, "Tapered = " << getTapered());
}
+ if (!hole->BaseProfileType.isReadOnly()) {
+ FCMD_OBJ_CMD(hole, "BaseProfileType = " << getBaseProfileType());
+ }
isApplying = false;
}
diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.h b/src/Mod/PartDesign/Gui/TaskHoleParameters.h
index 1af45a2d41..f3f5980740 100644
--- a/src/Mod/PartDesign/Gui/TaskHoleParameters.h
+++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.h
@@ -79,6 +79,7 @@ public:
bool getModelThread() const;
long getThreadDepthType() const;
double getThreadDepth() const;
+ int getBaseProfileType() const;
private Q_SLOTS:
void threadedChanged();
@@ -108,6 +109,7 @@ private Q_SLOTS:
void updateViewChanged(bool isChecked);
void threadDepthTypeChanged(int index);
void threadDepthChanged(double value);
+ void baseProfileTypeChanged(int index);
void setCutDiagram();
private:
diff --git a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui
index 97ecddad4b..9029aba1f5 100644
--- a/src/Mod/PartDesign/Gui/TaskHoleParameters.ui
+++ b/src/Mod/PartDesign/Gui/TaskHoleParameters.ui
@@ -33,7 +33,7 @@
6
-
-
+
10
@@ -918,7 +918,7 @@ Note that the calculation can take some time
0
-
-
+
6
@@ -967,7 +967,7 @@ Note that the calculation can take some time
-
-
+
0
@@ -1011,7 +1011,7 @@ Note that the calculation can take some time
-
-
+
6
@@ -1074,6 +1074,36 @@ Note that the calculation can take some time
+ -
+
+
-
+
+
+ Base profile types
+
+
+
+ -
+
+
-
+
+ Circles and arcs
+
+
+ -
+
+ Points, circles and arcs
+
+
+ -
+
+ Points
+
+
+
+
+
+