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/PrefWidgets.h
- Gui::PrefDoubleSpinBox - QDoubleSpinBox + Gui::PrefCheckBox + QWidget
Gui/PrefWidgets.h
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 + + + + + +