From 41645ab455d24dcda650377d8c044e0578e01529 Mon Sep 17 00:00:00 2001 From: Artem Zhidkov Date: Wed, 22 Apr 2020 11:55:13 +0300 Subject: [PATCH] Issue #3222: 1d fillet * Implement the 1D-fillet feature * Unit tests for 1D-fillet * Documentation update. * Translations to French. * Additional test cases for filleting several wires at once. * Stabilize the sequence of fillet results. * Process failed vertices and send the message to highlight them in 3D viewer. --- src/BuildPlugin/BuildPlugin_Wire.cpp | 5 +- src/FeaturesAPI/FeaturesAPI.i | 2 + src/FeaturesAPI/FeaturesAPI_Fillet.cpp | 141 ++- src/FeaturesAPI/FeaturesAPI_Fillet.h | 101 +- src/FeaturesPlugin/CMakeLists.txt | 18 + .../FeaturesPlugin_Fillet1D.cpp | 187 ++++ src/FeaturesPlugin/FeaturesPlugin_Fillet1D.h | 115 +++ src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp | 5 + .../FeaturesPlugin_Validators.cpp | 74 ++ .../FeaturesPlugin_Validators.h | 16 + src/FeaturesPlugin/FeaturesPlugin_msg_en.ts | 116 +++ src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts | 119 +++ src/FeaturesPlugin/FeaturesPlugin_msg_ru.ts | 116 +++ .../Test/TestFillet1D_ErrorMsg.py | 80 ++ .../Test/TestFillet1D_Vertices_1.py | 63 ++ .../Test/TestFillet1D_Vertices_2.py | 74 ++ .../Test/TestFillet1D_Vertices_3.py | 63 ++ .../Test/TestFillet1D_Vertices_4.py | 78 ++ .../Test/TestFillet1D_Vertices_5.py | 52 + .../Test/TestFillet1D_Vertices_6.py | 52 + .../Test/TestFillet1D_Vertices_7.py | 52 + .../Test/TestFillet1D_Vertices_8.py | 67 ++ .../Test/TestFillet1D_Vertices_9.py | 70 ++ .../Test/TestFillet1D_Wire_1.py | 52 + .../Test/TestFillet1D_Wire_2.py | 52 + .../Test/TestFillet1D_Wire_3.py | 52 + .../Test/TestFillet1D_Wire_4.py | 67 ++ .../Test/TestFillet1D_Wire_5.py | 59 ++ src/FeaturesPlugin/doc/FeaturesPlugin.rst | 1 + .../doc/TUI_fillet1DVertices.rst | 12 + src/FeaturesPlugin/doc/TUI_fillet1DWire.rst | 12 + .../doc/examples/fillet1d_vertices.py | 25 + .../doc/examples/fillet1d_wire.py | 23 + src/FeaturesPlugin/doc/fillet1dFeature.rst | 100 ++ .../doc/images/Fillet1DPanel_Vertices.png | Bin 0 -> 9437 bytes .../doc/images/Fillet1DPanel_Wire.png | Bin 0 -> 7806 bytes .../doc/images/Fillet1DResult_Vertices.png | Bin 0 -> 5512 bytes .../doc/images/Fillet1DResult_Wire.png | Bin 0 -> 5559 bytes src/FeaturesPlugin/doc/images/fillet1d.png | Bin 0 -> 462 bytes .../doc/images/fillet1d_points.png | Bin 0 -> 948 bytes .../doc/images/fillet1d_wire.png | Bin 0 -> 1142 bytes src/FeaturesPlugin/fillet1d_widget.xml | 40 + src/FeaturesPlugin/icons/fillet1d.png | Bin 0 -> 462 bytes src/FeaturesPlugin/icons/fillet1d_points.png | Bin 0 -> 948 bytes src/FeaturesPlugin/icons/fillet1d_wire.png | Bin 0 -> 1142 bytes src/FeaturesPlugin/plugin-Features.xml | 9 + .../FiltersPlugin_OppositeToEdge.cpp | 1 + src/GeomAPI/GeomAPI_Edge.cpp | 16 + src/GeomAPI/GeomAPI_Edge.h | 4 + src/GeomAPI/GeomAPI_WireExplorer.cpp | 19 +- src/GeomAPI/GeomAPI_WireExplorer.h | 10 +- src/GeomAlgoAPI/CMakeLists.txt | 2 + src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp | 216 +++++ src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h | 75 ++ src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.cpp | 39 + src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.h | 5 + src/GeomAlgoImpl/CMakeLists.txt | 2 + src/GeomAlgoImpl/GEOMImpl_Fillet1d.cxx | 909 ++++++++++++++++++ src/GeomAlgoImpl/GEOMImpl_Fillet1d.hxx | 147 +++ src/ModelAPI/ModelAPI_Events.cpp | 21 + src/ModelAPI/ModelAPI_Events.h | 27 + src/ModelHighAPI/ModelHighAPI_Macro.h | 2 +- 62 files changed, 3622 insertions(+), 43 deletions(-) create mode 100644 src/FeaturesPlugin/FeaturesPlugin_Fillet1D.cpp create mode 100644 src/FeaturesPlugin/FeaturesPlugin_Fillet1D.h create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_ErrorMsg.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_1.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_2.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_3.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_4.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_5.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_6.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_7.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_8.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Vertices_9.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Wire_1.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Wire_2.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Wire_3.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Wire_4.py create mode 100644 src/FeaturesPlugin/Test/TestFillet1D_Wire_5.py create mode 100644 src/FeaturesPlugin/doc/TUI_fillet1DVertices.rst create mode 100644 src/FeaturesPlugin/doc/TUI_fillet1DWire.rst create mode 100644 src/FeaturesPlugin/doc/examples/fillet1d_vertices.py create mode 100644 src/FeaturesPlugin/doc/examples/fillet1d_wire.py create mode 100644 src/FeaturesPlugin/doc/fillet1dFeature.rst create mode 100644 src/FeaturesPlugin/doc/images/Fillet1DPanel_Vertices.png create mode 100644 src/FeaturesPlugin/doc/images/Fillet1DPanel_Wire.png create mode 100644 src/FeaturesPlugin/doc/images/Fillet1DResult_Vertices.png create mode 100644 src/FeaturesPlugin/doc/images/Fillet1DResult_Wire.png create mode 100644 src/FeaturesPlugin/doc/images/fillet1d.png create mode 100644 src/FeaturesPlugin/doc/images/fillet1d_points.png create mode 100644 src/FeaturesPlugin/doc/images/fillet1d_wire.png create mode 100644 src/FeaturesPlugin/fillet1d_widget.xml create mode 100644 src/FeaturesPlugin/icons/fillet1d.png create mode 100644 src/FeaturesPlugin/icons/fillet1d_points.png create mode 100644 src/FeaturesPlugin/icons/fillet1d_wire.png create mode 100644 src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp create mode 100644 src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h create mode 100644 src/GeomAlgoImpl/GEOMImpl_Fillet1d.cxx create mode 100644 src/GeomAlgoImpl/GEOMImpl_Fillet1d.hxx diff --git a/src/BuildPlugin/BuildPlugin_Wire.cpp b/src/BuildPlugin/BuildPlugin_Wire.cpp index 124a03abb..f3d48bd8b 100644 --- a/src/BuildPlugin/BuildPlugin_Wire.cpp +++ b/src/BuildPlugin/BuildPlugin_Wire.cpp @@ -122,7 +122,10 @@ void BuildPlugin_Wire::execute() for (ListOfShape::const_iterator anIt = anEdges.cbegin(); anIt != anEdges.cend(); ++anIt) { std::shared_ptr anEdgeInList(new GeomAPI_Edge(*anIt)); if (anEdgeInList->isEqual(anEdgeInResult)) { - aResultBody->modified(anEdgeInList, anEdgeInResult); + if (anEdgeInList->isSame(anEdgeInResult)) + aResultBody->generated(anEdgeInResult, "Edge"); + else + aResultBody->modified(anEdgeInList, anEdgeInResult); break; } } diff --git a/src/FeaturesAPI/FeaturesAPI.i b/src/FeaturesAPI/FeaturesAPI.i index 3e1065175..d98babe2e 100644 --- a/src/FeaturesAPI/FeaturesAPI.i +++ b/src/FeaturesAPI/FeaturesAPI.i @@ -68,6 +68,8 @@ %shared_ptr(FeaturesAPI_ExtrusionCut) %shared_ptr(FeaturesAPI_ExtrusionFuse) %shared_ptr(FeaturesAPI_Fillet) +%shared_ptr(FeaturesAPI_Fillet1D) +%shared_ptr(FeaturesAPI_Fillet2D) %shared_ptr(FeaturesAPI_Intersection) %shared_ptr(FeaturesAPI_MultiRotation) %shared_ptr(FeaturesAPI_MultiTranslation) diff --git a/src/FeaturesAPI/FeaturesAPI_Fillet.cpp b/src/FeaturesAPI/FeaturesAPI_Fillet.cpp index c9b446b6b..332b58208 100644 --- a/src/FeaturesAPI/FeaturesAPI_Fillet.cpp +++ b/src/FeaturesAPI/FeaturesAPI_Fillet.cpp @@ -21,19 +21,111 @@ #include #include -////#include +#include #include +static GeomAPI_Shape::ShapeType typeOfSelection( + const std::list& theBaseObjects) +{ + std::string aType = theBaseObjects.empty() ? "SHAPE" : theBaseObjects.front().shapeType(); + return GeomAPI_Shape::shapeTypeByStr(aType); +} + +//================================================================================================== + FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr& theFeature) : ModelHighAPI_Interface(theFeature) +{ +} + +//================================================================================================== + +FeaturesAPI_Fillet1D::FeaturesAPI_Fillet1D(const std::shared_ptr& theFeature) + : FeaturesAPI_Fillet(theFeature) { initialize(); } -FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr& theFeature, - const std::list& theBaseObjects, - const ModelHighAPI_Double& theRadius) - : ModelHighAPI_Interface(theFeature) +FeaturesAPI_Fillet1D::FeaturesAPI_Fillet1D(const std::shared_ptr& theFeature, + const std::list& theBaseObjects, + const ModelHighAPI_Double& theRadius) + : FeaturesAPI_Fillet(theFeature) +{ + if (initialize()) { + setBase(theBaseObjects); + fillAttribute(theRadius, myradius); + + execIfBaseNotEmpty(); + } +} + +FeaturesAPI_Fillet1D::~FeaturesAPI_Fillet1D() +{ +} + +void FeaturesAPI_Fillet1D::setBase(const std::list& theBaseObjects) +{ + mybaseWires->clear(); + mybaseVertices->clear(); + + if (typeOfSelection(theBaseObjects) == GeomAPI_Shape::WIRE) { + fillAttribute(FeaturesPlugin_Fillet1D::CREATION_BY_WIRES(), mycreationMethod); + fillAttribute(theBaseObjects, mybaseWires); + } + else { + fillAttribute(FeaturesPlugin_Fillet1D::CREATION_BY_VERTICES(), mycreationMethod); + fillAttribute(theBaseObjects, mybaseVertices); + } + + execIfBaseNotEmpty(); +} + +void FeaturesAPI_Fillet1D::setRadius(const ModelHighAPI_Double& theRadius) +{ + fillAttribute(theRadius, myradius); + execIfBaseNotEmpty(); +} + +void FeaturesAPI_Fillet1D::execIfBaseNotEmpty() +{ + if (mybaseWires->size() > 0 || mybaseVertices->size() > 0) + execute(); +} + +void FeaturesAPI_Fillet1D::dump(ModelHighAPI_Dumper& theDumper) const +{ + FeaturePtr aBase = feature(); + const std::string& aDocName = theDumper.name(aBase->document()); + + AttributeSelectionListPtr anAttrObjects; + if (creationMethod()->value() == FeaturesPlugin_Fillet1D::CREATION_BY_WIRES()) + anAttrObjects = aBase->selectionList(FeaturesPlugin_Fillet1D::WIRE_LIST_ID()); + else if (creationMethod()->value() == FeaturesPlugin_Fillet1D::CREATION_BY_VERTICES()) + anAttrObjects = aBase->selectionList(FeaturesPlugin_Fillet1D::VERTEX_LIST_ID()); + + AttributeDoublePtr anAttrRadius = aBase->real(FeaturesPlugin_Fillet1D::RADIUS_ID()); + + theDumper << aBase << " = model.addFillet(" << aDocName << ", " << anAttrObjects + << ", " << anAttrRadius; + + if (!aBase->data()->version().empty()) + theDumper << ", keepSubResults = True"; + + theDumper << ")" << std::endl; +} + +//================================================================================================== + +FeaturesAPI_Fillet2D::FeaturesAPI_Fillet2D(const std::shared_ptr& theFeature) + : FeaturesAPI_Fillet(theFeature) +{ + initialize(); +} + +FeaturesAPI_Fillet2D::FeaturesAPI_Fillet2D(const std::shared_ptr& theFeature, + const std::list& theBaseObjects, + const ModelHighAPI_Double& theRadius) + : FeaturesAPI_Fillet(theFeature) { if (initialize()) { fillAttribute(FeaturesPlugin_Fillet::CREATION_METHOD_SINGLE_RADIUS(), mycreationMethod); @@ -44,11 +136,11 @@ FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr& } } -FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr& theFeature, - const std::list& theBaseObjects, - const ModelHighAPI_Double& theRadius1, - const ModelHighAPI_Double& theRadius2) - : ModelHighAPI_Interface(theFeature) +FeaturesAPI_Fillet2D::FeaturesAPI_Fillet2D(const std::shared_ptr& theFeature, + const std::list& theBaseObjects, + const ModelHighAPI_Double& theRadius1, + const ModelHighAPI_Double& theRadius2) + : FeaturesAPI_Fillet(theFeature) { if (initialize()) { fillAttribute(FeaturesPlugin_Fillet::CREATION_METHOD_VARYING_RADIUS(), mycreationMethod); @@ -60,12 +152,11 @@ FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr& } } -FeaturesAPI_Fillet::~FeaturesAPI_Fillet() +FeaturesAPI_Fillet2D::~FeaturesAPI_Fillet2D() { } -//================================================================================================== -void FeaturesAPI_Fillet::setBase(const std::list& theBaseObjects) +void FeaturesAPI_Fillet2D::setBase(const std::list& theBaseObjects) { mybaseObjects->clear(); fillAttribute(theBaseObjects, mybaseObjects); @@ -73,7 +164,7 @@ void FeaturesAPI_Fillet::setBase(const std::list& theBas execIfBaseNotEmpty(); } -void FeaturesAPI_Fillet::setRadius(const ModelHighAPI_Double& theRadius) +void FeaturesAPI_Fillet2D::setRadius(const ModelHighAPI_Double& theRadius) { fillAttribute(FeaturesPlugin_Fillet::CREATION_METHOD_SINGLE_RADIUS(), mycreationMethod); fillAttribute(theRadius, myradius); @@ -81,8 +172,8 @@ void FeaturesAPI_Fillet::setRadius(const ModelHighAPI_Double& theRadius) execIfBaseNotEmpty(); } -void FeaturesAPI_Fillet::setRadius(const ModelHighAPI_Double& theRadius1, - const ModelHighAPI_Double& theRadius2) +void FeaturesAPI_Fillet2D::setRadius(const ModelHighAPI_Double& theRadius1, + const ModelHighAPI_Double& theRadius2) { fillAttribute(FeaturesPlugin_Fillet::CREATION_METHOD_VARYING_RADIUS(), mycreationMethod); fillAttribute(theRadius1, mystartRadius); @@ -91,7 +182,7 @@ void FeaturesAPI_Fillet::setRadius(const ModelHighAPI_Double& theRadius1, execIfBaseNotEmpty(); } -void FeaturesAPI_Fillet::dump(ModelHighAPI_Dumper& theDumper) const +void FeaturesAPI_Fillet2D::dump(ModelHighAPI_Dumper& theDumper) const { FeaturePtr aBase = feature(); const std::string& aDocName = theDumper.name(aBase->document()); @@ -118,7 +209,7 @@ void FeaturesAPI_Fillet::dump(ModelHighAPI_Dumper& theDumper) const theDumper << ")" << std::endl; } -void FeaturesAPI_Fillet::execIfBaseNotEmpty() +void FeaturesAPI_Fillet2D::execIfBaseNotEmpty() { if (mybaseObjects->size() > 0) execute(); @@ -133,14 +224,20 @@ FilletPtr addFillet(const std::shared_ptr& thePart, const ModelHighAPI_Double& theRadius2, const bool keepSubResults) { - std::shared_ptr aFeature = thePart->addFeature(FeaturesAPI_Fillet::ID()); + GeomAPI_Shape::ShapeType aType = typeOfSelection(theBaseObjects); + bool is1D = aType == GeomAPI_Shape::WIRE || aType == GeomAPI_Shape::VERTEX; + + FeaturePtr aFeature = + thePart->addFeature(is1D ? FeaturesAPI_Fillet1D::ID() : FeaturesAPI_Fillet2D::ID()); if (!keepSubResults) aFeature->data()->setVersion(""); FilletPtr aFillet; - if (theRadius2.value() < 0.0) - aFillet.reset(new FeaturesAPI_Fillet(aFeature, theBaseObjects, theRadius1)); + if (is1D) + aFillet.reset(new FeaturesAPI_Fillet1D(aFeature, theBaseObjects, theRadius1)); + else if (theRadius2.value() < 0.0) + aFillet.reset(new FeaturesAPI_Fillet2D(aFeature, theBaseObjects, theRadius1)); else - aFillet.reset(new FeaturesAPI_Fillet(aFeature, theBaseObjects, theRadius1, theRadius2)); + aFillet.reset(new FeaturesAPI_Fillet2D(aFeature, theBaseObjects, theRadius1, theRadius2)); return aFillet; } diff --git a/src/FeaturesAPI/FeaturesAPI_Fillet.h b/src/FeaturesAPI/FeaturesAPI_Fillet.h index 37eeef688..7eac5d6c8 100644 --- a/src/FeaturesAPI/FeaturesAPI_Fillet.h +++ b/src/FeaturesAPI/FeaturesAPI_Fillet.h @@ -23,6 +23,7 @@ #include "FeaturesAPI.h" #include +#include #include #include @@ -35,27 +36,103 @@ class ModelHighAPI_Selection; /// \brief Interface for Fillet feature. class FeaturesAPI_Fillet: public ModelHighAPI_Interface { +public: + /// Destructor. + virtual ~FeaturesAPI_Fillet() {} + + virtual std::shared_ptr radius() const = 0; + + /// Modify base objects of the fillet. + virtual void setBase(const std::list& theBaseObjects) = 0; + + /// Modify fillet to have fixed radius + virtual void setRadius(const ModelHighAPI_Double& theRadius) = 0; + +protected: + FeaturesAPI_Fillet(const std::shared_ptr& theFeature); +}; + +/// Pointer on the fillet object. +typedef std::shared_ptr FilletPtr; + + +/// \class FeaturesAPI_Fillet1D +/// \ingroup CPPHighAPI +/// \brief Interface for Fillet1D feature - fillet on vertices of a wire. +class FeaturesAPI_Fillet1D : public FeaturesAPI_Fillet +{ public: /// Constructor without values. FEATURESAPI_EXPORT - explicit FeaturesAPI_Fillet(const std::shared_ptr& theFeature); + explicit FeaturesAPI_Fillet1D(const std::shared_ptr& theFeature); /// Constructor with values. FEATURESAPI_EXPORT - explicit FeaturesAPI_Fillet(const std::shared_ptr& theFeature, - const std::list& theBaseObjects, - const ModelHighAPI_Double& theRadius); + explicit FeaturesAPI_Fillet1D(const std::shared_ptr& theFeature, + const std::list& theBaseObjects, + const ModelHighAPI_Double& theRadius); + + /// Destructor. + FEATURESAPI_EXPORT + virtual ~FeaturesAPI_Fillet1D(); + + INTERFACE_4(FeaturesPlugin_Fillet1D::ID(), + creationMethod, FeaturesPlugin_Fillet1D::CREATION_METHOD(), + ModelAPI_AttributeString, + /** Creation method */, + baseWires, FeaturesPlugin_Fillet1D::WIRE_LIST_ID(), + ModelAPI_AttributeSelectionList, + /** Base objects */, + baseVertices, FeaturesPlugin_Fillet1D::VERTEX_LIST_ID(), + ModelAPI_AttributeSelectionList, + /** Base objects */, + radius, FeaturesPlugin_Fillet1D::RADIUS_ID(), + ModelAPI_AttributeDouble, + /** Value of the fixed radius fillet */) + + /// Modify base objects of the fillet. + FEATURESAPI_EXPORT + virtual void setBase(const std::list& theBaseObjects); + + /// Modify fillet to have fixed radius + FEATURESAPI_EXPORT + virtual void setRadius(const ModelHighAPI_Double& theRadius); + + /// Dump wrapped feature + FEATURESAPI_EXPORT + virtual void dump(ModelHighAPI_Dumper& theDumper) const; + +private: + void execIfBaseNotEmpty(); +}; + + +/// \class FeaturesAPI_Fillet2D +/// \ingroup CPPHighAPI +/// \brief Interface for Fillet feature - fillet edges on a solid. +class FeaturesAPI_Fillet2D : public FeaturesAPI_Fillet +{ +public: + /// Constructor without values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Fillet2D(const std::shared_ptr& theFeature); + + /// Constructor with values. + FEATURESAPI_EXPORT + explicit FeaturesAPI_Fillet2D(const std::shared_ptr& theFeature, + const std::list& theBaseObjects, + const ModelHighAPI_Double& theRadius); /// Constructor with values. FEATURESAPI_EXPORT - explicit FeaturesAPI_Fillet(const std::shared_ptr& theFeature, - const std::list& theBaseObjects, - const ModelHighAPI_Double& theRadius1, - const ModelHighAPI_Double& theRadius2); + explicit FeaturesAPI_Fillet2D(const std::shared_ptr& theFeature, + const std::list& theBaseObjects, + const ModelHighAPI_Double& theRadius1, + const ModelHighAPI_Double& theRadius2); /// Destructor. FEATURESAPI_EXPORT - virtual ~FeaturesAPI_Fillet(); + virtual ~FeaturesAPI_Fillet2D(); INTERFACE_5(FeaturesPlugin_Fillet::ID(), creationMethod, FeaturesPlugin_Fillet::CREATION_METHOD(), @@ -76,11 +153,11 @@ public: /// Modify base objects of the fillet. FEATURESAPI_EXPORT - void setBase(const std::list& theBaseObjects); + virtual void setBase(const std::list& theBaseObjects); /// Modify fillet to have fixed radius FEATURESAPI_EXPORT - void setRadius(const ModelHighAPI_Double& theRadius); + virtual void setRadius(const ModelHighAPI_Double& theRadius); /// Modify fillet to have varying radius FEATURESAPI_EXPORT @@ -94,8 +171,6 @@ private: void execIfBaseNotEmpty(); }; -/// Pointer on Fillet object. -typedef std::shared_ptr FilletPtr; /// \ingroup CPPHighAPI /// \brief Create Fillet feature. diff --git a/src/FeaturesPlugin/CMakeLists.txt b/src/FeaturesPlugin/CMakeLists.txt index 3dd2b67f8..fcd93d08c 100644 --- a/src/FeaturesPlugin/CMakeLists.txt +++ b/src/FeaturesPlugin/CMakeLists.txt @@ -57,6 +57,7 @@ SET(PROJECT_HEADERS FeaturesPlugin_MultiTranslation.h FeaturesPlugin_MultiRotation.h FeaturesPlugin_Fillet.h + FeaturesPlugin_Fillet1D.h FeaturesPlugin_Measurement.h FeaturesPlugin_FusionFaces.h FeaturesPlugin_RemoveResults.h @@ -103,6 +104,7 @@ SET(PROJECT_SOURCES FeaturesPlugin_MultiTranslation.cpp FeaturesPlugin_MultiRotation.cpp FeaturesPlugin_Fillet.cpp + FeaturesPlugin_Fillet1D.cpp FeaturesPlugin_Measurement.cpp FeaturesPlugin_FusionFaces.cpp FeaturesPlugin_RemoveResults.cpp @@ -140,6 +142,7 @@ SET(XML_RESOURCES multitranslation_widget.xml multirotation_widget.xml fillet_widget.xml + fillet1d_widget.xml measurement_widget.xml fusion_faces_widget.xml chamfer_widget.xml @@ -662,4 +665,19 @@ ADD_UNIT_TESTS(TestExtrusion.py Test2817.py Test19065.py Test19066.py + TestFillet1D_ErrorMsg.py + TestFillet1D_Vertices_1.py + TestFillet1D_Vertices_2.py + TestFillet1D_Vertices_3.py + TestFillet1D_Vertices_4.py + TestFillet1D_Vertices_5.py + TestFillet1D_Vertices_6.py + TestFillet1D_Vertices_7.py + TestFillet1D_Vertices_8.py + TestFillet1D_Vertices_9.py + TestFillet1D_Wire_1.py + TestFillet1D_Wire_2.py + TestFillet1D_Wire_3.py + TestFillet1D_Wire_4.py + TestFillet1D_Wire_5.py ) diff --git a/src/FeaturesPlugin/FeaturesPlugin_Fillet1D.cpp b/src/FeaturesPlugin/FeaturesPlugin_Fillet1D.cpp new file mode 100644 index 000000000..d62fdee30 --- /dev/null +++ b/src/FeaturesPlugin/FeaturesPlugin_Fillet1D.cpp @@ -0,0 +1,187 @@ +// Copyright (C) 2020 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +FeaturesPlugin_Fillet1D::FeaturesPlugin_Fillet1D() +{ +} + +void FeaturesPlugin_Fillet1D::initAttributes() +{ + data()->addAttribute(CREATION_METHOD(), ModelAPI_AttributeString::typeId()); + data()->addAttribute(WIRE_LIST_ID(), ModelAPI_AttributeSelectionList::typeId()); + data()->addAttribute(VERTEX_LIST_ID(), ModelAPI_AttributeSelectionList::typeId()); + data()->addAttribute(RADIUS_ID(), ModelAPI_AttributeDouble::typeId()); +} + +void FeaturesPlugin_Fillet1D::execute() +{ + ListOfShape aWires; + MapShapeSubs aVertices; + if (!baseShapes(aWires, aVertices)) + return; + + int aResultIndex = 0; + for (ListOfShape::iterator anIt = aWires.begin(); anIt != aWires.end(); ++anIt) + if (!performFillet(*anIt, aVertices[*anIt], aResultIndex++)) + break; + removeResults(aResultIndex); +} + +bool FeaturesPlugin_Fillet1D::baseShapes(ListOfShape& theWires, MapShapeSubs& theWireVertices) +{ + std::set aProcessedWires; + std::string aMethod = string(CREATION_METHOD())->value(); + if (aMethod == CREATION_BY_WIRES()) { + AttributeSelectionListPtr aSelList = selectionList(WIRE_LIST_ID()); + + int aNbSel = aSelList->size(); + for (int ind = 0; ind < aNbSel; ++ind) { + AttributeSelectionPtr aCurSel = aSelList->value(ind); + GeomShapePtr aWire = aCurSel->context()->shape(); + if (aProcessedWires.find(aWire) != aProcessedWires.end()) + continue; + + aProcessedWires.insert(aWire); + // get vertices applicable for fillet + GeomAlgoAPI_MapShapesAndAncestors aMapVE(aWire, GeomAPI_Shape::VERTEX, GeomAPI_Shape::EDGE); + const MapShapeToShapes& aSubshapes = aMapVE.map(); + std::set aFilletVertices; + for (MapShapeToShapes::const_iterator anIt = aSubshapes.begin(); + anIt != aSubshapes.end(); ++anIt) { + // vertex should have 2 adjacent edges + if (anIt->second.size() != 2) + continue; + + // skip vertices, which adjacent edges are not on the same plane + ListOfShape anEdges; + anEdges.insert(anEdges.end(), anIt->second.begin(), anIt->second.end()); + GeomPlanePtr aPlane = GeomAlgoAPI_ShapeTools::findPlane(anEdges); + if (!aPlane) + continue; + + // skip vertices, which smoothly connect adjacent edges + GeomEdgePtr anEdge1(new GeomAPI_Edge(anEdges.front())); + GeomEdgePtr anEdge2(new GeomAPI_Edge(anEdges.back())); + GeomVertexPtr aSharedVertex(new GeomAPI_Vertex(anIt->first)); + if (GeomAlgoAPI_ShapeTools::isTangent(anEdge1, anEdge2, aSharedVertex)) + continue; + + aFilletVertices.insert(anIt->first); + } + + if (aFilletVertices.empty()) { + setError("Wire has no vertices for fillet."); + return false; + } + + + // keep the sequence of wires and fillet vertices stable + theWires.push_back(aWire); + for (GeomAPI_WireExplorer anExp(aWire->wire()); anExp.more(); anExp.next()) { + GeomShapePtr aVertex = anExp.currentVertex(); + if (aFilletVertices.find(aVertex) != aFilletVertices.end()) + theWireVertices[aWire].push_back(aVertex); + } + } + } + else if (aMethod == CREATION_BY_VERTICES()) { + AttributeSelectionListPtr aSelList = selectionList(VERTEX_LIST_ID()); + int aNbSel = aSelList->size(); + for (int ind = 0; ind < aNbSel; ++ind) { + AttributeSelectionPtr aCurSel = aSelList->value(ind); + GeomShapePtr aWire = aCurSel->context()->shape(); + GeomShapePtr aVertex = aCurSel->value(); + + // keep the sequence of wires stable + if (aProcessedWires.find(aWire) == aProcessedWires.end()) { + theWires.push_back(aWire); + aProcessedWires.insert(aWire); + } + + theWireVertices[aWire].push_back(aVertex); + } + } + return true; +} + +bool FeaturesPlugin_Fillet1D::performFillet(const GeomShapePtr& theWire, + const ListOfShape& theVertices, + const int theResultIndex) +{ + double aRadius = real(RADIUS_ID())->value(); + + // perform fillet operation + std::shared_ptr aFilletBuilder( + new GeomAlgoAPI_Fillet1D(theWire, theVertices, aRadius)); + + bool isOk = true; + bool isSendMessage = !myFailedVertices.empty(); + myFailedVertices = aFilletBuilder->failedVertices(); + + std::string anError; + if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aFilletBuilder, getKind(), anError)) { + isOk = false; + // in case of vertices, the fillet completed, send message to highlight them in the viewer + isSendMessage = true; + bool isAllFailed = myFailedVertices.size() == theVertices.size(); + setError(anError, isAllFailed); + if (isAllFailed) + return isOk; + } + + if (isSendMessage) { + // send message to highlight the failed vertices + std::shared_ptr aMessage( + new ModelAPI_Fillet1DFailedMessage(Events_Loop::eventByName(EVENT_1DFILLET_FAILED))); + aMessage->setVertices(myFailedVertices); + Events_Loop::loop()->send(aMessage); + } + + static const std::string THE_PREFIX = "Fillet1D"; + + // store result + ResultBodyPtr aResult = document()->createBody(data(), theResultIndex); + ListOfShape anOriginal; + anOriginal.push_back(theWire); + FeaturesPlugin_Tools::loadModifiedShapes(aResult, anOriginal, ListOfShape(), + aFilletBuilder, aFilletBuilder->shape(), THE_PREFIX); + setResult(aResult, theResultIndex); + // store new edges generated from vertices + for (ListOfShape::const_iterator anIt = theVertices.begin(); anIt != theVertices.end(); ++anIt) + aResult->loadGeneratedShapes(aFilletBuilder, *anIt, GeomAPI_Shape::VERTEX, THE_PREFIX, true); + + return isOk; +} diff --git a/src/FeaturesPlugin/FeaturesPlugin_Fillet1D.h b/src/FeaturesPlugin/FeaturesPlugin_Fillet1D.h new file mode 100644 index 000000000..7cf37cc23 --- /dev/null +++ b/src/FeaturesPlugin/FeaturesPlugin_Fillet1D.h @@ -0,0 +1,115 @@ +// Copyright (C) 2020 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef FeaturesPlugin_Fillet1D_H_ +#define FeaturesPlugin_Fillet1D_H_ + +#include + +#include +#include + +#include + +/// \class FeaturesPlugin_Fillet1D +/// \ingroup Plugins +/// \brief Feature for appling fillet on vertices of 3D wire. +class FeaturesPlugin_Fillet1D : public ModelAPI_Feature +{ + typedef std::map MapShapeSubs; + +public: + /// Feature kind. + inline static const std::string& ID() + { + static const std::string MY_ID("Fillet1D"); + return MY_ID; + } + + /// \return the kind of a feature. + FEATURESPLUGIN_EXPORT virtual const std::string& getKind() + { + static std::string MY_KIND = FeaturesPlugin_Fillet1D::ID(); + return MY_KIND; + } + + inline static const std::string& CREATION_METHOD() + { + static std::string MY_CREATION_METHOD("creation_method"); + return MY_CREATION_METHOD; + } + + inline static const std::string CREATION_BY_WIRES() + { + static std::string MY_SINGLE_RADIUS("by_wires"); + return MY_SINGLE_RADIUS; + } + + inline static const std::string CREATION_BY_VERTICES() + { + static std::string MY_VARYING_RADIUS("by_vertices"); + return MY_VARYING_RADIUS; + } + + /// Attribute name of selected wires. + inline static const std::string& WIRE_LIST_ID() + { + static const std::string MY_OBJECT_LIST_ID("main_wires"); + return MY_OBJECT_LIST_ID; + } + + /// Attribute name of selected vertices. + inline static const std::string& VERTEX_LIST_ID() + { + static const std::string MY_OBJECT_LIST_ID("main_vertices"); + return MY_OBJECT_LIST_ID; + } + + /// Attribute name of radius. + inline static const std::string& RADIUS_ID() + { + static const std::string MY_RADIUS_ID("radius"); + return MY_RADIUS_ID; + } + + /// Request for initialization of data model of the feature: adding all attributes. + FEATURESPLUGIN_EXPORT virtual void initAttributes(); + + /// Performs the fillet algorithm and stores it in the data structure. + FEATURESPLUGIN_EXPORT virtual void execute(); + + /// Use plugin manager for features creation. + FeaturesPlugin_Fillet1D(); + +private: + /// Get the list of wires and fillet vertices + /// \retun \c false if errors occured + bool baseShapes(ListOfShape& theWires, MapShapeSubs& theWireVertices); + + /// Run fillet operation and store result. + /// \return \c false if fillet failed. + bool performFillet(const GeomShapePtr& theWire, + const ListOfShape& theVertices, + const int theResultIndex); + +private: + ListOfShape myFailedVertices; +}; + +#endif diff --git a/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp b/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp index 9cf7af9d9..0d49dfe08 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp +++ b/src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -101,6 +102,8 @@ FeaturesPlugin_Plugin::FeaturesPlugin_Plugin() new FeaturesPlugin_ValidatorConcealedResult); aFactory->registerValidator("FeaturesPlugin_ValidatorFilletSelection", new FeaturesPlugin_ValidatorFilletSelection); + aFactory->registerValidator("FeaturesPlugin_ValidatorFillet1DSelection", + new FeaturesPlugin_ValidatorFillet1DSelection); aFactory->registerValidator("FeaturesPlugin_ValidatorCircular", new FeaturesPlugin_ValidatorCircular); aFactory->registerValidator("FeaturesPlugin_ValidatorBooleanArguments", @@ -180,6 +183,8 @@ FeaturePtr FeaturesPlugin_Plugin::createFeature(std::string theFeatureID) return FeaturePtr(new FeaturesPlugin_MultiRotation); } else if (theFeatureID == FeaturesPlugin_Fillet::ID()) { return FeaturePtr(new FeaturesPlugin_Fillet); + } else if (theFeatureID == FeaturesPlugin_Fillet1D::ID()) { + return FeaturePtr(new FeaturesPlugin_Fillet1D); } else if (theFeatureID == FeaturesPlugin_Measurement::ID()) { return FeaturePtr(new FeaturesPlugin_Measurement); } else if (theFeatureID == FeaturesPlugin_RemoveResults::ID()) { diff --git a/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp b/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp index e92148495..e84cae418 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp +++ b/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp @@ -54,6 +54,7 @@ #include #include +#include #include #include #include @@ -941,6 +942,79 @@ bool FeaturesPlugin_ValidatorFilletSelection::isValid(const AttributePtr& theAtt return true; } + +//================================================================================================== +bool FeaturesPlugin_ValidatorFillet1DSelection::isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + Events_InfoMessage& theError) const +{ + AttributeSelectionListPtr anAttrSelectionList = + std::dynamic_pointer_cast(theAttribute); + if (!anAttrSelectionList.get()) { + // LCOV_EXCL_START + theError = + "Error: This validator can only work with selection list attributes in \"Fillet\" feature."; + return false; + // LCOV_EXCL_STOP + } + + // check each selected vertex is a sharp corner between adjacent edges, + // and these edges are in the same plane + std::map aWireSubshapes; + int aNbSel = anAttrSelectionList->size(); + for (int ind = 0; ind < aNbSel; ++ind) { + AttributeSelectionPtr aCurSel = anAttrSelectionList->value(ind); + GeomShapePtr aContext = aCurSel->context()->shape(); + GeomShapePtr aVertex = aCurSel->value(); + // check wire already processed, if not, store all vertices and edges, sharing them + std::map::iterator aProcessed = aWireSubshapes.find(aContext); + if (aProcessed == aWireSubshapes.end()) { + if (aContext->shapeType() != GeomAPI_Shape::WIRE) { + theError = "Selected vertex is not a wire corner"; + return false; + } + if (aVertex->shapeType() != GeomAPI_Shape::VERTEX) { + theError = "Selected shape is not a vertex"; + return false; + } + + GeomAlgoAPI_MapShapesAndAncestors aMapVE(aContext, GeomAPI_Shape::VERTEX, + GeomAPI_Shape::EDGE); + aWireSubshapes[aContext] = aMapVE.map(); + aProcessed = aWireSubshapes.find(aContext); + } + + // check the vertex + MapShapeToShapes::iterator aFound = aProcessed->second.find(aVertex); + if (aFound == aProcessed->second.end()) { + theError = "Selected vertex does not exist in the wire"; + return true; + } + else if (aFound->second.size() != 2) { + theError = "Vertex should be shared between 2 edges exactly"; + return false; + } + + ListOfShape anEdges; + anEdges.insert(anEdges.end(), aFound->second.begin(), aFound->second.end()); + GeomPlanePtr aPlane = GeomAlgoAPI_ShapeTools::findPlane(anEdges); + if (!aPlane) { + theError = "Error: Edges are not planar"; + return false; + } + + GeomEdgePtr anEdge1(new GeomAPI_Edge(anEdges.front())); + GeomEdgePtr anEdge2(new GeomAPI_Edge(anEdges.back())); + GeomVertexPtr aSharedVertex(new GeomAPI_Vertex(aVertex)); + if (GeomAlgoAPI_ShapeTools::isTangent(anEdge1, anEdge2, aSharedVertex)) { + theError = "Error: Edges are tangent"; + return false; + } + } + + return true; +} + //================================================================================================== bool FeaturesPlugin_ValidatorPartitionSelection::isValid(const AttributePtr& theAttribute, const std::list& theArguments, diff --git a/src/FeaturesPlugin/FeaturesPlugin_Validators.h b/src/FeaturesPlugin/FeaturesPlugin_Validators.h index 59eb5fd43..951d4c52f 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_Validators.h +++ b/src/FeaturesPlugin/FeaturesPlugin_Validators.h @@ -188,6 +188,22 @@ public: Events_InfoMessage& theError) const; }; +/// \class FeaturesPlugin_ValidatorFillet1DSelection +/// \ingroup Validators +/// \brief Validates selection for 1d-fillet operation. +class FeaturesPlugin_ValidatorFillet1DSelection : public ModelAPI_AttributeValidator +{ +public: + /// \return True if the attribute is valid. It checks whether the selection + /// is acceptable for fillet on wire (vertex is a sharp corner). + /// \param[in] theAttribute an attribute to check. + /// \param[in] theArguments a filter parameters. + /// \param[out] theError error message. + virtual bool isValid(const AttributePtr& theAttribute, + const std::list& theArguments, + Events_InfoMessage& theError) const; +}; + /// \class FeaturesPlugin_ValidatorPartitionSelection /// \ingroup Validators /// \brief Validates selection for partition. diff --git a/src/FeaturesPlugin/FeaturesPlugin_msg_en.ts b/src/FeaturesPlugin/FeaturesPlugin_msg_en.ts index 0ff1004ee..c65a3e3ed 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_msg_en.ts +++ b/src/FeaturesPlugin/FeaturesPlugin_msg_en.ts @@ -3629,4 +3629,120 @@ Base shape is not selected. + + + + Fillet1D + + 1D-fillet + 1D-fillet + + + Perform fillet on vertices of a wire + Perform fillet on vertices of a wire + + + Wire has no vertices for fillet. + Wire has no vertices for fillet. + + + + Fillet1D:main_wires + + Wires + Wires + + + Select wires + Select wires + + + Attribute "%1" is not initialized. + Select wires. + + + Fillet each sharp corner of the wire + Fillet each sharp corner of the wire + + + + Fillet1D:main_vertices + + Vertices + Vertices + + + Select vertices + Select vertices + + + Attribute "%1" is not initialized. + Select vertices on wires. + + + Fillet the specified corners of the wire + Fillet the specified corners of the wire + + + + Fillet1D:main_vertices:FeaturesPlugin_ValidatorFillet1DSelection + + Selected vertex is not a wire corner + Selected vertex is not a wire corner + + + Selected shape is not a vertex + Selected shape is not a vertex + + + Selected vertex does not exist in the wire + Selected vertex does not exist in the wire + + + Vertex should be shared between 2 edges exactly + Vertex should be shared between 2 edges exactly + + + Error: Edges are not planar + Error: Edges are not planar + + + Error: Edges are tangent + Error: Edges are tangent + + + + Fillet1D:creation_method + + Wires + Wires + + + Vertices + Vertices + + + + Fillet1D:radius + + Radius + Radius + + + Fillet radius + Fillet radius + + + Attribute "%1" is not initialized. + Specify fillet radius. + + + + Fillet1D:radius:GeomValidators_Positive + + Value is too small. + Value is too small. + + + diff --git a/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts b/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts index 0dfcdfacf..6f821a2e2 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts +++ b/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts @@ -41,6 +41,10 @@ Fillet Congé + + 1D-fillet + 1D-congé + Fuse Fusionner @@ -964,6 +968,121 @@ + + + Fillet1D + + 1D-fillet + 1D-congé + + + Perform fillet on vertices of a wire + Effectuer un congé sur les sommets d'un contour + + + Wire has no vertices for fillet. + Le contour n'a pas de sommet pour le congé. + + + + Fillet1D:main_wires + + Wires + Contours + + + Select wires + Sélectionnez les contours + + + Attribute "%1" is not initialized. + Sélectionnez les contours. + + + Fillet each sharp corner of the wire + Raccordez chaque coin pointu du contour + + + + Fillet1D:main_vertices + + Vertices + Sommets + + + Select vertices + Sélectionnez les sommets + + + Attribute "%1" is not initialized. + Sélectionnez des sommets sur les contours. + + + Fillet the specified corners of the wire + Raccorder les coins spécifiés du contour + + + + Fillet1D:main_vertices:FeaturesPlugin_ValidatorFillet1DSelection + + Selected vertex is not a wire corner + Le sommet sélectionné n'est pas un coin de contour + + + Selected shape is not a vertex + La forme sélectionnée n'est pas un sommet + + + Selected vertex does not exist in the wire + Le sommet sélectionné n'existe pas dans le contour + + + Vertex should be shared between 2 edges exactly + Le sommet doit être partagé entre 2 bords exactement + + + Error: Edges are not planar + Erreur: les arêtes ne sont pas planes + + + Error: Edges are tangent + Erreur: les bords sont tangents + + + + Fillet1D:creation_method + + Wires + Contours + + + Vertices + Sommets + + + + Fillet1D:radius + + Radius + Rayon + + + Fillet radius + Rayon de congé + + + Attribute "%1" is not initialized. + Spécifiez le rayon du congé. + + + + Fillet1D:radius:GeomValidators_Positive + + Value is too small. + La valeur est trop petite. + + + Fuse diff --git a/src/FeaturesPlugin/FeaturesPlugin_msg_ru.ts b/src/FeaturesPlugin/FeaturesPlugin_msg_ru.ts index 4e52f4527..e27770699 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_msg_ru.ts +++ b/src/FeaturesPlugin/FeaturesPlugin_msg_ru.ts @@ -177,4 +177,120 @@ Ошибка: не все выбранные объекты являются подэлементами твердых тел. + + + + Fillet1D + + 1D-fillet + 1D-сглаживание + + + Perform fillet on vertices of a wire + Выполнить сглаживание в узлах контура + + + Wire has no vertices for fillet. + Контур не имеет узлов, подходящих для сглаживания. + + + + Fillet1D:main_wires + + Wires + Контуры + + + Select wires + Выберите контуры + + + Attribute "%1" is not initialized. + Выберите контуры. + + + Fillet each sharp corner of the wire + Сгладить все острые углы контура + + + + Fillet1D:main_vertices + + Vertices + Узлы + + + Select vertices + Выберите узлы + + + Attribute "%1" is not initialized. + Выберите острые углы на контуре. + + + Fillet the specified corners of the wire + Сгладить выбранные узлы контура + + + + Fillet1D:main_vertices:FeaturesPlugin_ValidatorFillet1DSelection + + Selected vertex is not a wire corner + Выбранный узел не принадлежит контуру + + + Selected shape is not a vertex + Выбранный объект не является узлом + + + Selected vertex does not exist in the wire + Выбранный узел не принадлежит контуру + + + Vertex should be shared between 2 edges exactly + Узел должен примыкать ровно к двум рёбрам + + + Error: Edges are not planar + Ошибка: рёбра не лежат на одной плоскости + + + Error: Edges are tangent + Ошибка: рёбра касаются друг друга + + + + Fillet1D:creation_method + + Wires + Контуры + + + Vertices + Узлы + + + + Fillet1D:radius + + Radius + Радиус + + + Fillet radius + Радиус сглаживания + + + Attribute "%1" is not initialized. + Задайте радиус сглаживания. + + + + Fillet1D:radius:GeomValidators_Positive + + Value is too small. + Значение радиуса слишком мало. + + + diff --git a/src/FeaturesPlugin/Test/TestFillet1D_ErrorMsg.py b/src/FeaturesPlugin/Test/TestFillet1D_ErrorMsg.py new file mode 100644 index 000000000..d491c3700 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_ErrorMsg.py @@ -0,0 +1,80 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Sketch +Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY")) + +### Create SketchLine +SketchLine_1 = Sketch_1.addLine(35.66344827586208, 18.52827586206897, -10.58758620689655, 18.52827586206897) +Sketch_1.setHorizontal(SketchLine_1.result()) + +### Create SketchProjection +SketchProjection_1 = Sketch_1.addProjection(model.selection("EDGE", "PartSet/OY"), False) +SketchLine_2 = SketchProjection_1.createdFeature() + +### Create SketchArc +SketchArc_1 = Sketch_1.addArc(-10.58758620689655, 34.59034482758621, -10.58758620689655, 18.52827586206897, 0, 46.66896551724138, True) +Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchArc_1.startPoint()) +Sketch_1.setTangent(SketchLine_1.result(), SketchArc_1.results()[1]) +Sketch_1.setCoincident(SketchLine_2.result(), SketchArc_1.endPoint()) +model.do() + +### Create Sketch +Sketch_2 = model.addSketch(Part_1_doc, model.defaultPlane("YOZ")) + +### Create SketchLine +SketchLine_3 = Sketch_2.addLine(46.66896551724138, 0, 49.59252615395944, 57.3324122669822) + +### Create SketchProjection +SketchProjection_2 = Sketch_2.addProjection(model.selection("VERTEX", "Sketch_1/SketchArc_1_2_StartVertex"), False) +SketchPoint_1 = SketchProjection_2.createdFeature() +Sketch_2.setCoincident(SketchLine_3.startPoint(), SketchPoint_1.result()) +model.do() + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "Sketch_1/SketchLine_1"), model.selection("EDGE", "Sketch_1/SketchArc_1_2"), model.selection("EDGE", "Sketch_2/SketchLine_3")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) +model.end() + + +### Check errors on 1D-fillet +vertices = ["Sketch_1/SketchLine_1_StartVertex", + "Sketch_2/SketchLine_3_EndVertex", + "[Wire_1_1/Modified_Edge&Sketch_1/SketchLine_1]e[Wire_1_1/Modified_Edge&Sketch_1/SketchArc_1_2]e", + "[Wire_1_1/Modified_Edge&Sketch_1/SketchArc_1_2]e[Wire_1_1/Modified_Edge&Sketch_2/SketchLine_3]e"] +for v in vertices: + model.begin() + Fillet1D = model.addFillet(Part_1_doc, [model.selection("VERTEX", v)], 1) + model.end() + assert(Fillet1D.feature().error() != "") + +### Wire has no vertices applicable for 1D-fillet +model.begin() +Fillet1D = model.addFillet(Part_1_doc, [model.selection("WIRE", "Wire_1_1")], 1) +model.end() +assert(Fillet1D.feature().error() != "") diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_1.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_1.py new file mode 100644 index 000000000..078f4950c --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_1.py @@ -0,0 +1,63 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Right][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Top]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Edge_1]e[Wire_1_1/Edge_2]e")], 2) + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [6]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [12]) +model.testResultsVolumes(Fillet1D_1, [0]) + +### Create Fillet1D +Fillet1D_2 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[(Wire_1_1/Edge_4)(Wire_1_1/Edge_5)2_Fillet1D_1_1]e[Wire_1_1/Edge_4]e")], 2) + +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_2, 1) +model.testNbSubResults(Fillet1D_2, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.EDGE, [7]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.VERTEX, [14]) +model.testResultsVolumes(Fillet1D_2, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_2.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_2.py new file mode 100644 index 000000000..295f22663 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_2.py @@ -0,0 +1,74 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Right]"), model.selection("EDGE", "[Box_1_1/Right][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Right]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Left]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Edge_4]e[Wire_1_1/Edge_6]e")], 2) + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [9]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [18]) +model.testResultsVolumes(Fillet1D_1, [0]) + +### Create Fillet1D +Fillet1D_2 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Fillet1D_1_1/ME:Fillet1D&Wire_1_1/Edge_1]e[Fillet1D_1_1/ME:Fillet1D&Wire_1_1/Edge_3]e")], 1) + +model.testNbResults(Fillet1D_2, 1) +model.testNbSubResults(Fillet1D_2, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.EDGE, [10]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.VERTEX, [20]) +model.testResultsVolumes(Fillet1D_2, [0]) + +### Create Fillet1D +Fillet1D_3 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Fillet1D_2_1/ME:Fillet1D&Wire_1_1/Edge_2]e[Fillet1D_2_1/ME:Fillet1D&Wire_1_1/Edge_4]e")], 5) + +model.testHaveNamingByType(Fillet1D_3, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_3, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_3, 1) +model.testNbSubResults(Fillet1D_3, [0]) +model.testNbSubShapes(Fillet1D_3, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_3, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_3, GeomAPI_Shape.EDGE, [11]) +model.testNbSubShapes(Fillet1D_3, GeomAPI_Shape.VERTEX, [22]) +model.testResultsVolumes(Fillet1D_3, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_3.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_3.py new file mode 100644 index 000000000..2d1a78b7a --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_3.py @@ -0,0 +1,63 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Cylinder +Cylinder_1 = model.addCylinder(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), model.selection("EDGE", "PartSet/OZ"), 5, 10, 45) +Wire_1_objects = [model.selection("EDGE", "[Cylinder_1_1/Face_1][Cylinder_1_1/Face_2]"), model.selection("EDGE", "[Cylinder_1_1/Face_2][Cylinder_1_1/Face_5]"), model.selection("EDGE", "[Cylinder_1_1/Face_4][Cylinder_1_1/Face_5]"), model.selection("EDGE", "[Cylinder_1_1/Face_3][Cylinder_1_1/Face_4]"), model.selection("EDGE", "[Cylinder_1_1/Face_1][Cylinder_1_1/Face_3]")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Edge_1]e[Wire_1_1/Edge_2]e")], 1) + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [6]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [12]) +model.testResultsVolumes(Fillet1D_1, [0]) + +### Create Fillet1D +Fillet1D_2 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Fillet1D_1_1/ME:Fillet1D&Wire_1_1/Edge_4]e[Fillet1D_1_1/ME:Fillet1D&Wire_1_1/Edge_3]e")], 3) + +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_2, 1) +model.testNbSubResults(Fillet1D_2, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.EDGE, [7]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.VERTEX, [14]) +model.testResultsVolumes(Fillet1D_2, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_4.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_4.py new file mode 100644 index 000000000..9112fcda4 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_4.py @@ -0,0 +1,78 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Sketch +Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY")) + +### Create SketchLine +SketchLine_1 = Sketch_1.addLine(-21.09850585253612, 7.838733928946021, 28.18352301808598, -36.47460859551143) + +### Create SketchLine +SketchLine_2 = Sketch_1.addLine(28.18352301808598, -36.47460859551143, -24.86780397022334, -36.47460859551143) +Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint()) +Sketch_1.setHorizontal(SketchLine_2.result()) + +### Create SketchArc +SketchArc_1 = Sketch_1.addArc(-24.86780397022334, -14.15762886307242, -24.86780397022334, -36.47460859551143, -21.09850585253612, 7.838733928946021, True) +Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchArc_1.startPoint()) +Sketch_1.setTangent(SketchLine_2.result(), SketchArc_1.results()[1]) +Sketch_1.setCoincident(SketchArc_1.endPoint(), SketchLine_1.startPoint()) +model.do() +Wire_1_objects = [model.selection("EDGE", "Sketch_1/SketchLine_1"), model.selection("EDGE", "Sketch_1/SketchLine_2"), model.selection("EDGE", "Sketch_1/SketchArc_1_2")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Modified_Edge&Sketch_1/SketchLine_1]e[Wire_1_1/Modified_Edge&Sketch_1/SketchLine_2]e")], 2) + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [4]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [8]) +model.testResultsVolumes(Fillet1D_1, [0]) + +### Create Fillet1D +Fillet1D_2 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Fillet1D_1_1/ME:Fillet1D&Sketch_1/SketchLine_1]e[_weak_name_1_Fillet1D_1_1]e")], 20) + +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_2, 1) +model.testNbSubResults(Fillet1D_2, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.EDGE, [5]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.VERTEX, [10]) +model.testResultsVolumes(Fillet1D_2, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_5.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_5.py new file mode 100644 index 000000000..a2406906f --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_5.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Right][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Top]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Edge_2]e[Wire_1_1/Edge_3]e"), model.selection("VERTEX", "[Wire_1_1/Edge_3]e[Wire_1_1/Edge_4]e")], 2) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [7]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [14]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_6.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_6.py new file mode 100644 index 000000000..46ffa866a --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_6.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Right]"), model.selection("EDGE", "[Box_1_1/Right][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Right]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Left]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Edge_4]e[Wire_1_1/Edge_6]e"), model.selection("VERTEX", "[Wire_1_1/Edge_2]e[Wire_1_1/Edge_4]e"), model.selection("VERTEX", "[Wire_1_1/Edge_1]e[Wire_1_1/Edge_3]e")], 2) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [11]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [22]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_7.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_7.py new file mode 100644 index 000000000..777ecc0ac --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_7.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Cylinder +Cylinder_1 = model.addCylinder(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), model.selection("EDGE", "PartSet/OZ"), 5, 10, 45) +Wire_1_objects = [model.selection("EDGE", "[Cylinder_1_1/Face_1][Cylinder_1_1/Face_2]"), model.selection("EDGE", "[Cylinder_1_1/Face_2][Cylinder_1_1/Face_5]"), model.selection("EDGE", "[Cylinder_1_1/Face_4][Cylinder_1_1/Face_5]"), model.selection("EDGE", "[Cylinder_1_1/Face_3][Cylinder_1_1/Face_4]"), model.selection("EDGE", "[Cylinder_1_1/Face_1][Cylinder_1_1/Face_3]")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Edge_1]e[Wire_1_1/Edge_2]e"), model.selection("VERTEX", "[Wire_1_1/Edge_3]e[Wire_1_1/Edge_4]e")], 2) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [7]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [14]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_8.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_8.py new file mode 100644 index 000000000..ac9bdcf21 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_8.py @@ -0,0 +1,67 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Sketch +Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY")) + +### Create SketchLine +SketchLine_1 = Sketch_1.addLine(-21.09850585253612, 7.838733928946021, 28.18352301808598, -36.47460859551143) + +### Create SketchLine +SketchLine_2 = Sketch_1.addLine(28.18352301808598, -36.47460859551143, -24.86780397022334, -36.47460859551143) +Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint()) +Sketch_1.setHorizontal(SketchLine_2.result()) + +### Create SketchArc +SketchArc_1 = Sketch_1.addArc(-24.86780397022334, -14.15762886307242, -24.86780397022334, -36.47460859551143, -21.09850585253612, 7.838733928946021, True) +Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchArc_1.startPoint()) +Sketch_1.setTangent(SketchLine_2.result(), SketchArc_1.results()[1]) +Sketch_1.setCoincident(SketchArc_1.endPoint(), SketchLine_1.startPoint()) +model.do() +Wire_1_objects = [model.selection("EDGE", "Sketch_1/SketchLine_1"), model.selection("EDGE", "Sketch_1/SketchLine_2"), model.selection("EDGE", "Sketch_1/SketchArc_1_2")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Modified_Edge&Sketch_1/SketchLine_1]e[Wire_1_1/Modified_Edge&Sketch_1/SketchLine_2]e"), model.selection("VERTEX", "[Wire_1_1/Modified_Edge&Sketch_1/SketchLine_1]e[Wire_1_1/Modified_Edge&Sketch_1/SketchArc_1_2]e")], 15) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [5]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [10]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Vertices_9.py b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_9.py new file mode 100644 index 000000000..2305d0cc3 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Vertices_9.py @@ -0,0 +1,70 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Recover +Recover_1 = model.addRecover(Part_1_doc, Wire_1, [Box_1.result()]) + +### Create Wire +Wire_2_objects = [model.selection("EDGE", "[Recover_1_1/Modified_Face&Box_1_1/Right][Recover_1_1/Modified_Face&Box_1_1/Bottom]"), model.selection("EDGE", "[Recover_1_1/Modified_Face&Box_1_1/Back][Recover_1_1/Modified_Face&Box_1_1/Right]"), model.selection("EDGE", "[Recover_1_1/Modified_Face&Box_1_1/Back][Recover_1_1/Modified_Face&Box_1_1/Top]")] +Wire_2 = model.addWire(Part_1_doc, Wire_2_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Wire_1_1/Edge_1]e[Wire_1_1/Edge_2]e"), model.selection("VERTEX", "[Wire_2_1/Edge_2]e[Wire_2_1/Edge_3]e")], 2) + +model.testNbResults(Fillet1D_1, 2) +model.testNbSubResults(Fillet1D_1, [0, 0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0, 0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0, 0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [4, 4]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [8, 8]) +model.testResultsVolumes(Fillet1D_1, [0, 0]) + +### Create Fillet1D +Fillet1D_2 = model.addFillet(Part_1_doc, [model.selection("VERTEX", "[Fillet1D_1_2/ME:Fillet1D&Wire_2_1/Edge_1]e[Fillet1D_1_2/ME:Fillet1D&Wire_2_1/Edge_2]e"), model.selection("VERTEX", "[Fillet1D_1_1/ME:Fillet1D&Wire_1_1/Edge_2]e[Fillet1D_1_1/ME:Fillet1D&Wire_1_1/Edge_3]e")], 5) + +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_2, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_2, 2) +model.testNbSubResults(Fillet1D_2, [0, 0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.SOLID, [0, 0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.FACE, [0, 0]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.EDGE, [5, 5]) +model.testNbSubShapes(Fillet1D_2, GeomAPI_Shape.VERTEX, [10, 10]) +model.testResultsVolumes(Fillet1D_2, [0, 0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Wire_1.py b/src/FeaturesPlugin/Test/TestFillet1D_Wire_1.py new file mode 100644 index 000000000..aaad4ecdf --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Wire_1.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Right][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Top]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("WIRE", "Wire_1_1")], 2) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [9]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [18]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Wire_2.py b/src/FeaturesPlugin/Test/TestFillet1D_Wire_2.py new file mode 100644 index 000000000..dea6667ea --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Wire_2.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Right]"), model.selection("EDGE", "[Box_1_1/Right][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Right]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Top]"), model.selection("EDGE", "[Box_1_1/Back][Box_1_1/Left]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("WIRE", "Wire_1_1")], 2) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [16]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [32]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Wire_3.py b/src/FeaturesPlugin/Test/TestFillet1D_Wire_3.py new file mode 100644 index 000000000..834dd2b1a --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Wire_3.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Cylinder +Cylinder_1 = model.addCylinder(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), model.selection("EDGE", "PartSet/OZ"), 5, 10, 45) +Wire_1_objects = [model.selection("EDGE", "[Cylinder_1_1/Face_1][Cylinder_1_1/Face_2]"), model.selection("EDGE", "[Cylinder_1_1/Face_2][Cylinder_1_1/Face_5]"), model.selection("EDGE", "[Cylinder_1_1/Face_4][Cylinder_1_1/Face_5]"), model.selection("EDGE", "[Cylinder_1_1/Face_3][Cylinder_1_1/Face_4]"), model.selection("EDGE", "[Cylinder_1_1/Face_1][Cylinder_1_1/Face_3]")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("WIRE", "Wire_1_1")], 1) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [9]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [18]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Wire_4.py b/src/FeaturesPlugin/Test/TestFillet1D_Wire_4.py new file mode 100644 index 000000000..5249526f7 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Wire_4.py @@ -0,0 +1,67 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Sketch +Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY")) + +### Create SketchLine +SketchLine_1 = Sketch_1.addLine(-21.09850585253612, 7.838733928946021, 28.18352301808598, -36.47460859551143) + +### Create SketchLine +SketchLine_2 = Sketch_1.addLine(28.18352301808598, -36.47460859551143, -24.86780397022334, -36.47460859551143) +Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint()) +Sketch_1.setHorizontal(SketchLine_2.result()) + +### Create SketchArc +SketchArc_1 = Sketch_1.addArc(-24.86780397022334, -14.15762886307242, -24.86780397022334, -36.47460859551143, -21.09850585253612, 7.838733928946021, True) +Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchArc_1.startPoint()) +Sketch_1.setTangent(SketchLine_2.result(), SketchArc_1.results()[1]) +Sketch_1.setCoincident(SketchArc_1.endPoint(), SketchLine_1.startPoint()) +model.do() +Wire_1_objects = [model.selection("EDGE", "Sketch_1/SketchLine_1"), model.selection("EDGE", "Sketch_1/SketchLine_2"), model.selection("EDGE", "Sketch_1/SketchArc_1_2")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("WIRE", "Wire_1_1")], 2) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 1) +model.testNbSubResults(Fillet1D_1, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [5]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [10]) +model.testResultsVolumes(Fillet1D_1, [0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/Test/TestFillet1D_Wire_5.py b/src/FeaturesPlugin/Test/TestFillet1D_Wire_5.py new file mode 100644 index 000000000..b05c0c685 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestFillet1D_Wire_5.py @@ -0,0 +1,59 @@ +# Copyright (C) 2020 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from salome.shaper import model +from GeomAPI import * + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]")] + +### Create Wire +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Recover +Recover_1 = model.addRecover(Part_1_doc, Wire_1, [Box_1.result()]) + +### Create Wire +Wire_2_objects = [model.selection("EDGE", "[Recover_1_1/Modified_Face&Box_1_1/Right][Recover_1_1/Modified_Face&Box_1_1/Bottom]"), model.selection("EDGE", "[Recover_1_1/Modified_Face&Box_1_1/Back][Recover_1_1/Modified_Face&Box_1_1/Right]"), model.selection("EDGE", "[Recover_1_1/Modified_Face&Box_1_1/Back][Recover_1_1/Modified_Face&Box_1_1/Top]")] +Wire_2 = model.addWire(Part_1_doc, Wire_2_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("WIRE", "Wire_1_1"), model.selection("WIRE", "Wire_2_1")], 2) + +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.VERTEX) +model.testHaveNamingByType(Fillet1D_1, model, Part_1_doc, GeomAPI_Shape.EDGE) +model.end() + +model.testNbResults(Fillet1D_1, 2) +model.testNbSubResults(Fillet1D_1, [0, 0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.SOLID, [0, 0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.FACE, [0, 0]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.EDGE, [5, 5]) +model.testNbSubShapes(Fillet1D_1, GeomAPI_Shape.VERTEX, [10, 10]) +model.testResultsVolumes(Fillet1D_1, [0, 0]) + +assert(model.checkPythonDump(model.ModelHighAPI.CHECK_NAMING)) diff --git a/src/FeaturesPlugin/doc/FeaturesPlugin.rst b/src/FeaturesPlugin/doc/FeaturesPlugin.rst index 9b06e64e5..421292b0b 100644 --- a/src/FeaturesPlugin/doc/FeaturesPlugin.rst +++ b/src/FeaturesPlugin/doc/FeaturesPlugin.rst @@ -19,6 +19,7 @@ Features plug-in provides a set of common topological operations. It implements extrusionCutFeature.rst extrusionFeature.rst extrusionFuseFeature.rst + fillet1dFeature.rst filletFeature.rst fuseFeature.rst fuseFeatureFaces.rst diff --git a/src/FeaturesPlugin/doc/TUI_fillet1DVertices.rst b/src/FeaturesPlugin/doc/TUI_fillet1DVertices.rst new file mode 100644 index 000000000..6a7f0372c --- /dev/null +++ b/src/FeaturesPlugin/doc/TUI_fillet1DVertices.rst @@ -0,0 +1,12 @@ + + .. _tui_create_fillet1d_vertices: + +Create 1D-fillet for special vertices on a wire +=============================================== + +.. literalinclude:: examples/fillet1d_vertices.py + :linenos: + :language: python + +:download:`Download this script ` + diff --git a/src/FeaturesPlugin/doc/TUI_fillet1DWire.rst b/src/FeaturesPlugin/doc/TUI_fillet1DWire.rst new file mode 100644 index 000000000..c22230616 --- /dev/null +++ b/src/FeaturesPlugin/doc/TUI_fillet1DWire.rst @@ -0,0 +1,12 @@ + + .. _tui_create_fillet1d_wire: + +Create 1D-fillet on a wire +========================== + +.. literalinclude:: examples/fillet1d_wire.py + :linenos: + :language: python + +:download:`Download this script ` + diff --git a/src/FeaturesPlugin/doc/examples/fillet1d_vertices.py b/src/FeaturesPlugin/doc/examples/fillet1d_vertices.py new file mode 100644 index 000000000..d2a0cbe5a --- /dev/null +++ b/src/FeaturesPlugin/doc/examples/fillet1d_vertices.py @@ -0,0 +1,25 @@ +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), + model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), + model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), + model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Right]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1_objects = [model.selection("VERTEX", "[Wire_1_1/Edge_2]e[Wire_1_1/Edge_3]e"), + model.selection("VERTEX", "[Wire_1_1/Edge_3]e[Wire_1_1/Edge_4]e")] +Fillet1D_1 = model.addFillet(Part_1_doc, Fillet1D_1_objects, 3) + +model.end() diff --git a/src/FeaturesPlugin/doc/examples/fillet1d_wire.py b/src/FeaturesPlugin/doc/examples/fillet1d_wire.py new file mode 100644 index 000000000..36ccfc975 --- /dev/null +++ b/src/FeaturesPlugin/doc/examples/fillet1d_wire.py @@ -0,0 +1,23 @@ +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Box +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) + +### Create Wire +Wire_1_objects = [model.selection("EDGE", "[Box_1_1/Left][Box_1_1/Bottom]"), + model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Left]"), + model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]"), + model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Right]")] +Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False) + +### Create Fillet1D +Fillet1D_1 = model.addFillet(Part_1_doc, [model.selection("WIRE", "Wire_1_1")], 3) + +model.end() diff --git a/src/FeaturesPlugin/doc/fillet1dFeature.rst b/src/FeaturesPlugin/doc/fillet1dFeature.rst new file mode 100644 index 000000000..e3a30a404 --- /dev/null +++ b/src/FeaturesPlugin/doc/fillet1dFeature.rst @@ -0,0 +1,100 @@ +.. |fillet1d.icon| image:: images/fillet1d.png + +.. _featureFillet1D: + +1D-fillet +========= + +**1D-fillet** feature creates fillets on the vertices of a wire. +The fillet may be performed on sharp corners of a wire, which are shared exactly between 2 edges, and which are placed ona single plane. + +To create a 1D-fillet in the active part: + +#. select in the Main Menu *Feature - > 1D-fillet* item or +#. click |fillet1d.icon| **1D-fillet** button in the toolbar + +There are 2 types of fillet: + + .. image:: images/fillet1d_wire.png + :align: left + fillet all sharp corners on a wire + + .. image:: images/fillet1d_points.png + :align: left + fillet only the specified corners + +Fillet a wire +------------- + +The property panel for this mode is shown below. + +.. image:: images/Fillet1DPanel_Wire.png + :align: center + +.. centered:: + Property panel of an operation to fillet all sharp corners on a wire + +Input fields: + +- **Wires** panel contains the list of wires for the operation. The fillet will be performed to the applicable corners. If the wire has no such corner, the error message will be shown; +- **Radius** defines the fillet radius. + +**TUI Command**: + +.. py:function:: model.addFillet(Part_doc, [wires], radius) + + :param part: The current part object. + :param list: A list of wires subject to fillet operation in format *model.selection(TYPE, shape)*. + :param number: Radius value. + :return: Created object. + +Result +"""""" + +Result of **Fillet a wire** is shown below. In this case all vertices were sharp, thus, filleted. + +.. image:: images/Fillet1DResult_Wire.png + :align: center + +.. centered:: + Result of filleting a wire + +**See Also** a sample TUI Script of :ref:`tui_create_fillet1d_wire` operation. + +Fillet the specified vertices on a wire +--------------------------------------- + +Alternatively, there is a possibility to create a fillet on a special sharp corners of a wire. + +.. image:: images/Fillet1DPanel_Vertices.png + :align: center + +.. centered:: + Property panel to fillet the specified vertices of a wire + +Input fields: + +- **Vertices** panel contains list of vertices on a wires applicable for fillet operation; +- **Radius** defines the fillet radius. + +**TUI Command**: + +.. py:function:: model.addFillet(Part_doc, [vertices], radius) + + :param part: The current part object. + :param list: A list of vertices subject to fillet operation in format *model.selection(TYPE, shape)*. + :param number: Radius value. + :return: Created object. + +Result +"""""" + +Result of **Fillet by vertices** is shown below. The only 2 corners of the wire become smooth. + +.. image:: images/Fillet1DResult_Vertices.png + :align: center + +.. centered:: + Result of filleting the specified vertices of a wire + +**See Also** a sample TUI Script of :ref:`tui_create_fillet1d_vertices` operation. diff --git a/src/FeaturesPlugin/doc/images/Fillet1DPanel_Vertices.png b/src/FeaturesPlugin/doc/images/Fillet1DPanel_Vertices.png new file mode 100644 index 0000000000000000000000000000000000000000..883c5e03a68081e644c167143167a5a1e0591ad5 GIT binary patch literal 9437 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 BShN5D literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/Fillet1DPanel_Wire.png b/src/FeaturesPlugin/doc/images/Fillet1DPanel_Wire.png new file mode 100644 index 0000000000000000000000000000000000000000..668627e0a7cd04f33915f8a3ce8de431844ded0a GIT binary patch literal 7806 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 MdZ%=Yezzw64OV literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/fillet1d.png b/src/FeaturesPlugin/doc/images/fillet1d.png new file mode 100644 index 0000000000000000000000000000000000000000..a94aba335ca23bdebe65712f59c0ef89439c42ba GIT binary patch literal 462 zcmV;<0WtoGP)4}NOEf5< z4K0pA%>^#4%`GiCH@3JGL2Zp85{6ns4uz#=iGHss$te7B)4|>6#c|@UMt5vWl4__l zr4=uqo!&D*S+-b7Rb~;Eyz1-^5(aQowSoYG5hwxZIg|KEpo9@-fZF`w`r~WEDWYU8 zTXG+t-ds78t~)3R5%xd|rTWu>Ub6ul0TT!{gnX+lzPE;F5g13n81QZ#8LUipo5Q)8$N?EJ&Np#Txd z0XS_ZI22$^I|pDaGaS%rjobk$%5XqAldb~~pza{R7dI+!KM;^IzW@LL07*qoM6N<$ Ef@40k^8f$< literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/doc/images/fillet1d_points.png b/src/FeaturesPlugin/doc/images/fillet1d_points.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f751a926ba1290f377df8d2a52f879d7732daf GIT binary patch literal 948 zcmV;l155mgP)vrsE+)>*+{`BcB$1hjO zj+ap1@l`9x72w0XrAgb&efeZ;Y|al);44UvU!D}4{lGJKy!plR*1?<8Q+{~@pTH98 zW65n8t^wN*X43pL{SRSv0BsW%C(Gscg}N`k{^;cHeGeVhs^)%|Dk8pw+3<#D^%#iU z_LCQWAL0svHNQ14>g)Xq=eio3f*KgSGi~+EJjyTTrY{3;yV}N};sDk5n|$#sIDLL{ zjf(ni8pTj!S$X0d6S8-G84#6$at%Z*+nEioc?}GJX%ZGL_|27%V?9x0tcSpwoGn_@ zW54}bt}#C_k0%B*D8GB6j-IH5I4rkfIpqyYTQuJNX~hhr{}`T9_{+0kYv37QvH<`i za@xJ1=r@h=?z)N@n2FdUzysG>)f>w`{(l?rOv8qbIXg2x z+*?ky1$PunZRqSesH!PfXaQu($K5^`;etcxZ072vd#mhy$FU_6q53&<4`^JC1W*;d zE|~t>h&^&QmCE|aS;YVa8&f_?s?_Y5d87imRLii84 WO*J6cJr5cH0000${H?<~OLrb=~r!ufx zlxhUrfLjy|$<(nXoKat|d4BCWgs?lAhKmBoEh}>b&vku)q9)EYfv_VP0A)5X%kNKh z+*ikysLgS%+nS+O4Rm~SDj5Ka*}#tBJDq>q_0j>CE5<4}4W*tQYHvK7Omou!g0m{; z-rZMxH*YZDN~L>68UmNC5pDBOdsBC^%}q;iwg|cix)|tESM+$#nyRXd|Gn`vfZi7c z5Of)_xk{F*w%K!F#;~S`jP;Vh0M940+wcsqetIe29$8ly_7oN`n7^~HH?r;OmGSh6 z;3=yZpF*%rff4na`~s0fKq%Id8V;L+w~1lN434|3B0f;xfqp_raVG?z2weo(1%#&n zLZNvwrlpC*o1OyWq_A?8X@E(HU<0TpfTOpqw!6+q#8@y1uo%W{arqGZ$ZYRLNx&w! zN(8+vb|(pXL~uO3ED+Nm`nj19&R3KE9VaRUA&?_+SjEc*0C3&ua>Uz~6<~isamCq; z45Is@KXt{uB{|K^$m^>u62tfd&^G*Yhv$a|5XDyIEe}+R;u8=hs0OPoc=&2d%ji-A zc*+BfC<4*7f(wIfO^<00?s9HWs}<~vgd78&iogN4uQqe3ntOemb_G8Pef@~ycL1N?kzQ* zWSY6`WTQg4;%eA{O`5@u^t@v4%Dj!8W4)I~%mO^R z+WXmv-H{(%i{kvX*5;`j^^9HM_W4q*M#^@9y}+6|YK9SxMhxTNjUSH9dmRg7-y+J= z^0jvz@8{(+Vz+_t>`;5t@mT?)$}KB%1huqn0aGqueBe$6z>$An?O!5 zIa7?0pXYyd`D473NQ(C&+b}M`I1yHX8xpRa`=0vdd!wbKE_2lqi3plet zwgPVgdNe4aRODnFQ2b1l(_$V z?DMDKtX|5-|IwE@1utILS9J(_ub_tneOS;>3(h_AU;yXSzt^gJT(1IPZvX%Q07*qo IM6N<$g4dK1y8r+H literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/fillet1d_widget.xml b/src/FeaturesPlugin/fillet1d_widget.xml new file mode 100644 index 000000000..e7f3cafa1 --- /dev/null +++ b/src/FeaturesPlugin/fillet1d_widget.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/FeaturesPlugin/icons/fillet1d.png b/src/FeaturesPlugin/icons/fillet1d.png new file mode 100644 index 0000000000000000000000000000000000000000..a94aba335ca23bdebe65712f59c0ef89439c42ba GIT binary patch literal 462 zcmV;<0WtoGP)4}NOEf5< z4K0pA%>^#4%`GiCH@3JGL2Zp85{6ns4uz#=iGHss$te7B)4|>6#c|@UMt5vWl4__l zr4=uqo!&D*S+-b7Rb~;Eyz1-^5(aQowSoYG5hwxZIg|KEpo9@-fZF`w`r~WEDWYU8 zTXG+t-ds78t~)3R5%xd|rTWu>Ub6ul0TT!{gnX+lzPE;F5g13n81QZ#8LUipo5Q)8$N?EJ&Np#Txd z0XS_ZI22$^I|pDaGaS%rjobk$%5XqAldb~~pza{R7dI+!KM;^IzW@LL07*qoM6N<$ Ef@40k^8f$< literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/icons/fillet1d_points.png b/src/FeaturesPlugin/icons/fillet1d_points.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f751a926ba1290f377df8d2a52f879d7732daf GIT binary patch literal 948 zcmV;l155mgP)vrsE+)>*+{`BcB$1hjO zj+ap1@l`9x72w0XrAgb&efeZ;Y|al);44UvU!D}4{lGJKy!plR*1?<8Q+{~@pTH98 zW65n8t^wN*X43pL{SRSv0BsW%C(Gscg}N`k{^;cHeGeVhs^)%|Dk8pw+3<#D^%#iU z_LCQWAL0svHNQ14>g)Xq=eio3f*KgSGi~+EJjyTTrY{3;yV}N};sDk5n|$#sIDLL{ zjf(ni8pTj!S$X0d6S8-G84#6$at%Z*+nEioc?}GJX%ZGL_|27%V?9x0tcSpwoGn_@ zW54}bt}#C_k0%B*D8GB6j-IH5I4rkfIpqyYTQuJNX~hhr{}`T9_{+0kYv37QvH<`i za@xJ1=r@h=?z)N@n2FdUzysG>)f>w`{(l?rOv8qbIXg2x z+*?ky1$PunZRqSesH!PfXaQu($K5^`;etcxZ072vd#mhy$FU_6q53&<4`^JC1W*;d zE|~t>h&^&QmCE|aS;YVa8&f_?s?_Y5d87imRLii84 WO*J6cJr5cH0000${H?<~OLrb=~r!ufx zlxhUrfLjy|$<(nXoKat|d4BCWgs?lAhKmBoEh}>b&vku)q9)EYfv_VP0A)5X%kNKh z+*ikysLgS%+nS+O4Rm~SDj5Ka*}#tBJDq>q_0j>CE5<4}4W*tQYHvK7Omou!g0m{; z-rZMxH*YZDN~L>68UmNC5pDBOdsBC^%}q;iwg|cix)|tESM+$#nyRXd|Gn`vfZi7c z5Of)_xk{F*w%K!F#;~S`jP;Vh0M940+wcsqetIe29$8ly_7oN`n7^~HH?r;OmGSh6 z;3=yZpF*%rff4na`~s0fKq%Id8V;L+w~1lN434|3B0f;xfqp_raVG?z2weo(1%#&n zLZNvwrlpC*o1OyWq_A?8X@E(HU<0TpfTOpqw!6+q#8@y1uo%W{arqGZ$ZYRLNx&w! zN(8+vb|(pXL~uO3ED+Nm`nj19&R3KE9VaRUA&?_+SjEc*0C3&ua>Uz~6<~isamCq; z45Is@KXt{uB{|K^$m^>u62tfd&^G*Yhv$a|5XDyIEe}+R;u8=hs0OPoc=&2d%ji-A zc*+BfC<4*7f(wIfO^<00?s9HWs}<~vgd78&iogN4uQqe3ntOemb_G8Pef@~ycL1N?kzQ* zWSY6`WTQg4;%eA{O`5@u^t@v4%Dj!8W4)I~%mO^R z+WXmv-H{(%i{kvX*5;`j^^9HM_W4q*M#^@9y}+6|YK9SxMhxTNjUSH9dmRg7-y+J= z^0jvz@8{(+Vz+_t>`;5t@mT?)$}KB%1huqn0aGqueBe$6z>$An?O!5 zIa7?0pXYyd`D473NQ(C&+b}M`I1yHX8xpRa`=0vdd!wbKE_2lqi3plet zwgPVgdNe4aRODnFQ2b1l(_$V z?DMDKtX|5-|IwE@1utILS9J(_ub_tneOS;>3(h_AU;yXSzt^gJT(1IPZvX%Q07*qo IM6N<$g4dK1y8r+H literal 0 HcmV?d00001 diff --git a/src/FeaturesPlugin/plugin-Features.xml b/src/FeaturesPlugin/plugin-Features.xml index 29f405333..0a336c398 100644 --- a/src/FeaturesPlugin/plugin-Features.xml +++ b/src/FeaturesPlugin/plugin-Features.xml @@ -116,6 +116,15 @@ + + + diff --git a/src/FiltersPlugin/FiltersPlugin_OppositeToEdge.cpp b/src/FiltersPlugin/FiltersPlugin_OppositeToEdge.cpp index 9d462d708..e7d65648a 100644 --- a/src/FiltersPlugin/FiltersPlugin_OppositeToEdge.cpp +++ b/src/FiltersPlugin/FiltersPlugin_OppositeToEdge.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include diff --git a/src/GeomAPI/GeomAPI_Edge.cpp b/src/GeomAPI/GeomAPI_Edge.cpp index 6233a08c9..41cfa84a8 100644 --- a/src/GeomAPI/GeomAPI_Edge.cpp +++ b/src/GeomAPI/GeomAPI_Edge.cpp @@ -461,6 +461,22 @@ void GeomAPI_Edge::setLastPointTolerance(const double theTolerance) BRep_Builder().UpdateVertex(aVLast, theTolerance); } +double GeomAPI_Edge::firstPointTolerance() const +{ + TopoDS_Edge anEdge = impl(); + TopoDS_Vertex aVFirst, aVLast; + TopExp::Vertices(anEdge, aVFirst, aVLast); + return BRep_Tool::Tolerance(aVFirst); +} + +double GeomAPI_Edge::lastPointTolerance() const +{ + TopoDS_Edge anEdge = impl(); + TopoDS_Vertex aVFirst, aVLast; + TopExp::Vertices(anEdge, aVFirst, aVLast); + return BRep_Tool::Tolerance(aVLast); +} + GeomPointPtr GeomAPI_Edge::middlePoint() const { GeomPointPtr aMiddlePoint; diff --git a/src/GeomAPI/GeomAPI_Edge.h b/src/GeomAPI/GeomAPI_Edge.h index 89eecc8e9..6accbf978 100644 --- a/src/GeomAPI/GeomAPI_Edge.h +++ b/src/GeomAPI/GeomAPI_Edge.h @@ -135,6 +135,10 @@ public: GEOMAPI_EXPORT void setLastPointTolerance(const double theTolerance); + GEOMAPI_EXPORT double firstPointTolerance() const; + + GEOMAPI_EXPORT double lastPointTolerance() const; + /// Return middle point on the edge GEOMAPI_EXPORT virtual std::shared_ptr middlePoint() const; diff --git a/src/GeomAPI/GeomAPI_WireExplorer.cpp b/src/GeomAPI/GeomAPI_WireExplorer.cpp index 318819f99..635c9b6fb 100644 --- a/src/GeomAPI/GeomAPI_WireExplorer.cpp +++ b/src/GeomAPI/GeomAPI_WireExplorer.cpp @@ -18,6 +18,9 @@ // #include + +#include +#include #include #include @@ -49,11 +52,19 @@ void GeomAPI_WireExplorer::next() MY_EXPLORER->Next(); } -std::shared_ptr GeomAPI_WireExplorer::current() +std::shared_ptr GeomAPI_WireExplorer::current() +{ + const TopoDS_Edge& aShape = MY_EXPLORER->Current(); + std::shared_ptr aGeomShape(new GeomAPI_Edge()); + aGeomShape->setImpl(new TopoDS_Edge(aShape)); + return aGeomShape; +} + +std::shared_ptr GeomAPI_WireExplorer::currentVertex() { - const TopoDS_Shape& aShape = MY_EXPLORER->Current(); - std::shared_ptr aGeomShape(new GeomAPI_Shape()); - aGeomShape->setImpl(new TopoDS_Shape(aShape)); + const TopoDS_Vertex& aShape = MY_EXPLORER->CurrentVertex(); + std::shared_ptr aGeomShape(new GeomAPI_Vertex()); + aGeomShape->setImpl(new TopoDS_Vertex(aShape)); return aGeomShape; } diff --git a/src/GeomAPI/GeomAPI_WireExplorer.h b/src/GeomAPI/GeomAPI_WireExplorer.h index 292128e90..97e6eb180 100644 --- a/src/GeomAPI/GeomAPI_WireExplorer.h +++ b/src/GeomAPI/GeomAPI_WireExplorer.h @@ -23,7 +23,9 @@ #include #include +class GeomAPI_Edge; class GeomAPI_Shape; +class GeomAPI_Vertex; class GeomAPI_Wire; /** \class GeomAPI_WireExplorer @@ -53,9 +55,13 @@ public: /// if there are no more shapes to explore. GEOMAPI_EXPORT void next(); - /// \return the current shape in the exploration or empty pointer + /// \return the current edge in the exploration or empty pointer /// if this explorer has no more shapes to explore. - GEOMAPI_EXPORT std::shared_ptr current(); + GEOMAPI_EXPORT std::shared_ptr current(); + + /// \return the current vertex in the exploration or empty pointer + /// if this explorer has no more shapes to explore. + GEOMAPI_EXPORT std::shared_ptr currentVertex(); /// Clears the content of the explorer. It will return False on more(). GEOMAPI_EXPORT void clear(); diff --git a/src/GeomAlgoAPI/CMakeLists.txt b/src/GeomAlgoAPI/CMakeLists.txt index 78fba30bc..95e749c10 100644 --- a/src/GeomAlgoAPI/CMakeLists.txt +++ b/src/GeomAlgoAPI/CMakeLists.txt @@ -75,6 +75,7 @@ SET(PROJECT_HEADERS GeomAlgoAPI_Circ2dBuilder.h GeomAlgoAPI_UnifySameDomain.h GeomAlgoAPI_Fillet.h + GeomAlgoAPI_Fillet1D.h GeomAlgoAPI_SortListOfShapes.h GeomAlgoAPI_Filling.h GeomAlgoAPI_CurveBuilder.h @@ -139,6 +140,7 @@ SET(PROJECT_SOURCES GeomAlgoAPI_Circ2dBuilder.cpp GeomAlgoAPI_UnifySameDomain.cpp GeomAlgoAPI_Fillet.cpp + GeomAlgoAPI_Fillet1D.cpp GeomAlgoAPI_SortListOfShapes.cpp GeomAlgoAPI_Filling.cpp GeomAlgoAPI_CurveBuilder.cpp diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp new file mode 100644 index 000000000..440822088 --- /dev/null +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp @@ -0,0 +1,216 @@ +// Copyright (C) 2020 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +static GeomShapePtr convert(const TopoDS_Shape& theShape) +{ + GeomShapePtr aNewShape(new GeomAPI_Shape); + aNewShape->setImpl(new TopoDS_Shape(theShape)); + return aNewShape; +} + +static void substituteNewEdge(GeomEdgePtr theEdge, + std::map& theMap) +{ + std::map::iterator anIt = theMap.begin(); + for (; anIt != theMap.end(); ++anIt) { + for (ListOfShape::iterator aEIt = anIt->second.begin(); aEIt != anIt->second.end(); ++aEIt) + if (theEdge->isEqual(*aEIt)) { + // substitute edge and stop iteration + *aEIt = theEdge; + return; + } + } +} + + +GeomAlgoAPI_Fillet1D::GeomAlgoAPI_Fillet1D(const GeomShapePtr& theBaseWire, + const ListOfShape& theFilletVertices, + const double theFilletRadius) +{ + build(theBaseWire, theFilletVertices, theFilletRadius); +} + +void GeomAlgoAPI_Fillet1D::build(const GeomShapePtr& theBaseWire, + const ListOfShape& theFilletVertices, + const double theRadius) +{ + if (!theBaseWire || theFilletVertices.empty() || theRadius < 0.) + return; + + myFailedVertices.clear(); + // store all edges of a base wire as modified, because they will be rebuild by ShapeFix + for (GeomAPI_WireExplorer aWExp(theBaseWire->wire()); aWExp.more(); aWExp.next()) { + GeomShapePtr aCurrent = aWExp.current(); + GeomAlgoAPI_Copy aCopy(aCurrent); + myModified[aCurrent].push_back(aCopy.shape()); + } + + GeomAlgoAPI_MapShapesAndAncestors aMapVE(theBaseWire, GeomAPI_Shape::VERTEX, + GeomAPI_Shape::EDGE); + + for (ListOfShape::const_iterator aVIt = theFilletVertices.begin(); + aVIt != theFilletVertices.end(); ++aVIt) { + // get edges to perform fillet + MapShapeToShapes::const_iterator aVE = aMapVE.map().find(*aVIt); + if (aVE == aMapVE.map().end()) + continue; + ListOfShape anEdges; + for (SetOfShapes::const_iterator aEIt = aVE->second.begin(); + aEIt != aVE->second.end(); ++aEIt) { + ListOfShape aNewEdges; + modified(*aEIt, aNewEdges); + if (aNewEdges.empty()) + anEdges.push_back(*aEIt); + else + anEdges.insert(anEdges.end(), aNewEdges.begin(), aNewEdges.end()); + } + + GeomPlanePtr aPlane = GeomAlgoAPI_ShapeTools::findPlane(anEdges); + if (!aPlane) + return; // non-planar edges + + TopoDS_Edge anEdge1 = TopoDS::Edge(anEdges.front()->impl()); + TopoDS_Edge anEdge2 = TopoDS::Edge(anEdges.back()->impl()); + + // create fillet builder + GEOMImpl_Fillet1d aFilletBuilder(anEdge1, anEdge2, aPlane->impl()); + if (!aFilletBuilder.Perform(theRadius)) { + // store the failed vertex and continue + myFailedVertices.push_back(*aVIt); + continue; + } + + GeomPointPtr aPoint = aVE->first->vertex()->point(); + TopoDS_Edge aFilletEdge = aFilletBuilder.Result(aPoint->impl(), anEdge1, anEdge2); + + // store modified shapes + myGenerated[aVE->first].push_back(convert(aFilletEdge)); + SetOfShapes::const_iterator aEIt = aVE->second.begin(); + myModified[*aEIt].clear(); + myModified[*aEIt].push_back(convert(anEdge1)); + myModified[*(++aEIt)].clear(); + myModified[*aEIt].push_back(convert(anEdge2)); + } + + // compose a new wire + TopoDS_Wire aNewWire; + BRep_Builder aBuilder; + aBuilder.MakeWire(aNewWire); + GeomWirePtr aBaseWire = theBaseWire->wire(); + GeomAPI_WireExplorer aWExp(aBaseWire); + GeomShapePtr aBaseFirstEdge = aWExp.current(); + for (; aWExp.more(); aWExp.next()) { + ListOfShape aNewEdges; + modified(aWExp.current(), aNewEdges); + if (aNewEdges.empty()) + aNewEdges.push_back(aWExp.current()); + for (ListOfShape::iterator anIt = aNewEdges.begin(); anIt != aNewEdges.end(); ++anIt) + aBuilder.Add(aNewWire, TopoDS::Edge((*anIt)->impl())); + } + for (MapModified::iterator aGenIt = myGenerated.begin(); aGenIt != myGenerated.end(); ++aGenIt) { + for (ListOfShape::iterator anIt = aGenIt->second.begin(); anIt != aGenIt->second.end(); ++anIt) + aBuilder.Add(aNewWire, TopoDS::Edge((*anIt)->impl())); + } + // fix the wire connectivity + ShapeFix_Wire aFixWire; + aFixWire.Load(aNewWire); + aFixWire.ClosedWireMode() = aBaseWire->isClosed(); + aFixWire.FixReorder(); + aNewWire = aFixWire.WireAPIMake(); + if (aNewWire.IsNull()) { + myFailedVertices = theFilletVertices; + return; + } + + // update the map of modified shapes, because the edges are changed by ShapeFix + for (BRepTools_WireExplorer anExp(aNewWire); anExp.More(); anExp.Next()) { + GeomEdgePtr aCurrent(new GeomAPI_Edge); + aCurrent->setImpl(new TopoDS_Edge(anExp.Current())); + substituteNewEdge(aCurrent, myGenerated); + substituteNewEdge(aCurrent, myModified); + } + + // rebuild the wire once again to get the first edge of fillet wire correspond + // to the first edge of original wire + TopoDS_Edge aFirstEdge = TopoDS::Edge(aBaseFirstEdge->impl()); + ListOfShape aNewEdges; + modified(aBaseFirstEdge, aNewEdges); + if (!aNewEdges.empty()) + aFirstEdge = TopoDS::Edge(aNewEdges.front()->impl()); + TopTools_ListOfShape aKeptForEnd; + BRepTools_WireExplorer aNewExp(aNewWire); + for (; aNewExp.More(); aNewExp.Next()) + if (aNewExp.Current().IsEqual(aFirstEdge)) + break; + if (aNewExp.More()) { + TopoDS_Wire aReorderedWire; + aBuilder.MakeWire(aReorderedWire); + for (; aNewExp.More(); aNewExp.Next()) + aBuilder.Add(aReorderedWire, aNewExp.Current()); + for (aNewExp.Init(aNewWire); aNewExp.More(); aNewExp.Next()) { + if (aNewExp.Current().IsEqual(aFirstEdge)) + break; + aBuilder.Add(aReorderedWire, aNewExp.Current()); + } + aNewWire = aReorderedWire; + } + + std::shared_ptr aShape(new GeomAPI_Shape()); + aShape->setImpl(new TopoDS_Shape(aNewWire)); + myModified[theBaseWire].push_back(aShape); + + setShape(aShape); + setDone(myFailedVertices.empty()); +} + +void GeomAlgoAPI_Fillet1D::generated(const GeomShapePtr theOldShape, + ListOfShape& theNewShapes) +{ + MapModified::iterator aFound = myGenerated.find(theOldShape); + if (aFound != myGenerated.end()) + theNewShapes = aFound->second; +} + +void GeomAlgoAPI_Fillet1D::modified(const GeomShapePtr theOldShape, + ListOfShape& theNewShapes) +{ + MapModified::iterator aFound = myModified.find(theOldShape); + if (aFound != myModified.end()) + theNewShapes = aFound->second; +} diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h new file mode 100644 index 000000000..578f91cc2 --- /dev/null +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h @@ -0,0 +1,75 @@ +// Copyright (C) 2020 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef GeomAlgoAPI_Fillet1D_H_ +#define GeomAlgoAPI_Fillet1D_H_ + +#include +#include + +#include + +/// \class GeomAlgoAPI_Fillet1D +/// \ingroup DataAlgo +/// \brief Perform fillet on vertices of a wire +class GeomAlgoAPI_Fillet1D : public GeomAlgoAPI_MakeShape +{ + typedef std::map MapModified; + +public: + /// Run fillet operation on a set of vertices with fixed radius. + /// \param theBaseWire a changing Wire + /// \param theFilletVertices list of edges the fillet is performed on + /// \param theFilletRadius radius of the fillet + GEOMALGOAPI_EXPORT GeomAlgoAPI_Fillet1D(const GeomShapePtr& theBaseWire, + const ListOfShape& theFilletVertices, + const double theFilletRadius); + + /// \return the list of shapes generated from the shape \a theShape. + /// \param[in] theOldShape base shape. + /// \param[out] theNewShapes shapes generated from \a theShape. Does not cleared! + GEOMALGOAPI_EXPORT virtual void generated(const GeomShapePtr theOldShape, + ListOfShape& theNewShapes); + + /// \return the list of shapes modified from the shape \a theShape. + /// \param[in] theOldShape base shape. + /// \param[out] theNewShapes shapes modified from \a theShape. Does not cleared! + GEOMALGOAPI_EXPORT virtual void modified(const GeomShapePtr theOldShape, + ListOfShape& theNewShapes); + + /// \return List of failed vertices + const ListOfShape& failedVertices() const { return myFailedVertices; } + +private: + /// Perform 1d-fillet on wire + /// \param theBaseWire a changing wire + /// \param theFilletVertices list of vertices of filler + /// \param theRadius fillet radius + void build(const GeomShapePtr& theBaseWire, + const ListOfShape& theFilletVertices, + const double theRadius); + +private: + MapModified myGenerated; + MapModified myModified; + + ListOfShape myFailedVertices; +}; + +#endif diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.cpp index d35f58bad..5b04d9f06 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include @@ -855,6 +856,44 @@ std::shared_ptr return anOuterWire; } +//================================================================================================== +static bool boundaryOfEdge(const std::shared_ptr theEdge, + const std::shared_ptr theVertex, + double& theParam) +{ + GeomPointPtr aPoint = theVertex->point(); + GeomPointPtr aFirstPnt = theEdge->firstPoint(); + double aFirstPntTol = theEdge->firstPointTolerance(); + GeomPointPtr aLastPnt = theEdge->lastPoint(); + double aLastPntTol = theEdge->lastPointTolerance(); + + double aFirst, aLast; + theEdge->getRange(aFirst, aLast); + + bool isFirst = aPoint->distance(aFirstPnt) <= aFirstPntTol; + bool isLast = aPoint->distance(aLastPnt) <= aLastPntTol; + if (isFirst) + theParam = aFirst; + else if (isLast) + theParam = aLast; + + return isFirst != isLast; +} + +bool GeomAlgoAPI_ShapeTools::isTangent(const std::shared_ptr theEdge1, + const std::shared_ptr theEdge2, + const std::shared_ptr theTgPoint) +{ + double aParE1 = 0, aParE2 = 0; + if (!boundaryOfEdge(theEdge1, theTgPoint, aParE1) || + !boundaryOfEdge(theEdge2, theTgPoint, aParE2)) + return false; + + BRepAdaptor_Curve aC1(theEdge1->impl()); + BRepAdaptor_Curve aC2(theEdge2->impl()); + return BRepLProp::Continuity(aC1, aC2, aParE1, aParE2) >= GeomAbs_G1; +} + //================================================================================================== bool GeomAlgoAPI_ShapeTools::isParallel(const std::shared_ptr theEdge, const std::shared_ptr theFace) diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.h b/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.h index e321dd3e3..4e1e6523a 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.h @@ -141,6 +141,11 @@ public: GEOMALGOAPI_EXPORT static std::shared_ptr getFaceOuterWire(const std::shared_ptr theFace); + /// \return \c true if edges are tangent in the specified point + GEOMALGOAPI_EXPORT static bool isTangent(const std::shared_ptr theEdge1, + const std::shared_ptr theEdge2, + const std::shared_ptr theTgPoint); + /// \return true if edge is parallel to face. GEOMALGOAPI_EXPORT static bool isParallel(const std::shared_ptr theEdge, const std::shared_ptr theFace); diff --git a/src/GeomAlgoImpl/CMakeLists.txt b/src/GeomAlgoImpl/CMakeLists.txt index a855e00ca..83c679959 100644 --- a/src/GeomAlgoImpl/CMakeLists.txt +++ b/src/GeomAlgoImpl/CMakeLists.txt @@ -22,10 +22,12 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) SET(PROJECT_HEADERS GeomAlgoImpl.h GEOMAlgo_Splitter.hxx + GEOMImpl_Fillet1d.hxx ) SET(PROJECT_SOURCES GEOMAlgo_Splitter.cxx + GEOMImpl_Fillet1d.cxx ) SET(PROJECT_LIBRARIES diff --git a/src/GeomAlgoImpl/GEOMImpl_Fillet1d.cxx b/src/GeomAlgoImpl/GEOMImpl_Fillet1d.cxx new file mode 100644 index 000000000..b9aef213c --- /dev/null +++ b/src/GeomAlgoImpl/GEOMImpl_Fillet1d.cxx @@ -0,0 +1,909 @@ +// Copyright (C) 2007-2020 CEA/DEN, EDF R&D, OPEN CASCADE +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : GEOMImpl_Fillet1d.cxx +// Module : GEOMImpl + +#include "GEOMImpl_Fillet1d.hxx" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/** + * This function returns Standard_True if it is possible to divide edge, i.e. + * if one parameter either start or end one is inside the edge. This function + * is used in the method GEOMImpl_Fillet1d::Result. + * + * \param theEdge the edge + * \param theStart the start parameter + * \param theEnd the end parameter + * \return Standard_True if it is possible to split edge; + * Standard_False otherwise. + */ +static Standard_Boolean IsDivideEdge(const TopoDS_Edge &theEdge, + const Standard_Real theStart, + const Standard_Real theEnd) +{ + Standard_Real aFirst; + Standard_Real aLast; + Handle(Geom_Curve) aCurve = BRep_Tool::Curve(theEdge, aFirst, aLast); + gp_Pnt aPStart = aCurve->Value(theStart); + gp_Pnt aPEnd = aCurve->Value(theEnd); + TopoDS_Vertex aVFirst = TopExp::FirstVertex(theEdge); + TopoDS_Vertex aVLast = TopExp::LastVertex(theEdge); + Standard_Real aTolFirst = BRep_Tool::Tolerance(aVFirst); + Standard_Real aTolLast = BRep_Tool::Tolerance(aVLast); + Standard_Real aTolConf = Precision::Confusion(); + gp_Pnt aPFirst = BRep_Tool::Pnt(aVFirst); + gp_Pnt aPLast = BRep_Tool::Pnt(aVLast); + Standard_Real aDistSF = aPStart.Distance(aPFirst); + Standard_Real aDistSL = aPStart.Distance(aPLast); + Standard_Real aDistEF = aPEnd.Distance(aPFirst); + Standard_Real aDistEL = aPEnd.Distance(aPLast); + Standard_Boolean isSplit = Standard_True; + + if (aDistSF <= aTolFirst + aTolConf || + aDistSL <= aTolLast + aTolConf) { + if (aDistEF <= aTolFirst + aTolConf || + aDistEL <= aTolLast + aTolConf) { + + isSplit = Standard_False; + // in this case the original edge is thrown, and distance (gap) from new arc end + // to a vertex of original wire can reach (aVertexTolerance + Precision::Confusion()). + // Resulting wire is fixed (Mantis issue 0023411) in GEOMImpl_Fillet1dDriver::MakeFillet() + } + } + + return isSplit; +} + +/** + * class GEOMImpl_Fillet1d + */ + +//======================================================================= +//function : Constructor +//purpose : +//======================================================================= +GEOMImpl_Fillet1d::GEOMImpl_Fillet1d(const TopoDS_Edge& theEdge1, + const TopoDS_Edge& theEdge2, + const gp_Pln& thePlane) +: myEdgesExchnged( Standard_False ) +{ + myPlane = new Geom_Plane(thePlane); + + BRepAdaptor_Curve aBAC1(theEdge1); + BRepAdaptor_Curve aBAC2(theEdge2); + if (aBAC1.GetType() < aBAC2.GetType()) + { // first curve must be more complicated + myEdge1 = theEdge2; + myEdge2 = theEdge1; + myEdgesExchnged = Standard_True; + } + else + { + myEdge1 = theEdge1; + myEdge2 = theEdge2; + } + + Handle(Geom_Curve) aCurve1 = BRep_Tool::Curve(myEdge1, myStart1, myEnd1); + Handle(Geom_Curve) aCurve2 = BRep_Tool::Curve(myEdge2, myStart2, myEnd2); + + myCurve1 = GeomProjLib::Curve2d(aCurve1, myStart1, myEnd1, myPlane); + myCurve2 = GeomProjLib::Curve2d(aCurve2, myStart2, myEnd2, myPlane); + + while (myCurve1->IsPeriodic() && myStart1 >= myEnd1) + myEnd1 += myCurve1->Period(); + while (myCurve2->IsPeriodic() && myStart2 >= myEnd2) + myEnd2 += myCurve2->Period(); + + if (aBAC1.GetType() == aBAC2.GetType()) + { + if (myEnd2 - myStart2 < myEnd1 - myStart1) + { // first curve must be parametrically shorter + TopoDS_Edge anEdge = myEdge1; + myEdge1 = myEdge2; + myEdge2 = anEdge; + Handle(Geom2d_Curve) aCurve = myCurve1; + myCurve1 = myCurve2; + myCurve2 = aCurve; + Standard_Real a = myStart1; + myStart1 = myStart2; + myStart2 = a; + a = myEnd1; + myEnd1 = myEnd2; + myEnd2 = a; + myEdgesExchnged = Standard_True; + } + } +} + +//======================================================================= +//function : isRadiusIntersected +//purpose : local function +//======================================================================= +static Standard_Boolean isRadiusIntersected(const Handle(Geom2d_Curve)& theCurve, + const gp_Pnt2d theStart, + const gp_Pnt2d theEnd, + const Standard_Boolean theStartConnected) +{ + const Standard_Real aTol = Precision::Confusion(); + const Standard_Real anAngTol = Precision::Angular(); + Handle(Geom2d_Line) aRadiusLine = new Geom2d_Line (theStart, gp_Dir2d(gp_Vec2d(theStart, theEnd))); + Geom2dAPI_InterCurveCurve anInter (theCurve, aRadiusLine, aTol); + Standard_Integer a; + gp_Pnt2d aPoint; + for(a = anInter.NbPoints(); a > 0; a--) + { + aPoint = anInter.Point(a); + if ( aPoint.Distance(theStart) < aTol && !theStartConnected ) + return Standard_True; + if (aPoint.Distance(theEnd) < aTol * 200) + return Standard_True; + if (gp_Vec2d(aPoint, theStart).IsOpposite(gp_Vec2d(aPoint, theEnd), anAngTol)) + return Standard_True; + } + Handle(Geom2d_Curve) aCurve; + for(a = anInter.NbSegments(); a > 0; a--) + { + // Porting to DEV version of OCCT 10.02.2017 BEGIN + Standard_NotImplemented::Raise("The treatment of tangential intersection is not implemented"); + // Porting to DEV version of OCCT 10.02.2017 END + } + return Standard_False; +} + + +//======================================================================= +//function : fillPoint +//purpose : +//======================================================================= +void GEOMImpl_Fillet1d::fillPoint(GEOMImpl_Fillet1dPoint* thePoint) +{ + gp_Pnt2d aPoint; + gp_Vec2d aVec; + const Standard_Real aTol = Precision::Confusion(); + myCurve1->D1(thePoint->GetParam(), aPoint, aVec); + if (aVec.SquareMagnitude() < aTol) + return; + + gp_Vec2d aPerp(((myStartSide)?-1:1) * aVec.Y(), ((myStartSide)?1:-1) * aVec.X()); + aPerp.Normalize(); + aPerp.Multiply(myRadius); + gp_Pnt2d aCenter = aPoint.Translated(aPerp); + thePoint->SetCenter(aCenter); + + // on the intersection point + Standard_Boolean aValid = Standard_True; + Geom2dAPI_ProjectPointOnCurve aProjInt(aPoint, myCurve2); + if (aProjInt.NbPoints() && aPoint.Distance(aProjInt.NearestPoint()) < aTol) + aValid = Standard_False; + else + aValid = !isRadiusIntersected(myCurve2, aPoint, aCenter, Standard_True); + + Geom2dAPI_ProjectPointOnCurve aProj(aCenter, myCurve2); + Standard_Integer a, aNB = aProj.NbPoints(); + for(a = aNB; a > 0; a--) + { + if (aPoint.Distance(aProj.Point(a)) < aTol) + continue; + + Standard_Boolean aValid2 = aValid; + if (aValid2) + aValid2 = !isRadiusIntersected(myCurve1, aCenter, aProj.Point(a), Standard_False); + + // checking the right parameter + Standard_Real aParam = aProj.Parameter(a); + while(myCurve2->IsPeriodic() && aParam < myStart2) + aParam += myCurve2->Period(); + + thePoint->AddValue(aProj.Distance(a) * aProj.Distance(a) - myRadius * myRadius, + (aParam >= myStart2 && aParam <= myEnd2 && aValid2)); + if (fabs(fabs(aProj.Distance(a)) - myRadius) < aTol) + thePoint->SetParam2(aParam); + } +} + +//======================================================================= +//function : fillDiff +//purpose : +//======================================================================= +void GEOMImpl_Fillet1d::fillDiff(GEOMImpl_Fillet1dPoint* thePoint, Standard_Real theDiffStep, Standard_Boolean theFront) +{ + GEOMImpl_Fillet1dPoint* aDiff = + new GEOMImpl_Fillet1dPoint(thePoint->GetParam() + (theFront?(theDiffStep):(-theDiffStep))); + fillPoint(aDiff); + if (!thePoint->ComputeDifference(aDiff)) + { + aDiff->SetParam(thePoint->GetParam() + (theFront?(-theDiffStep):(theDiffStep))); + fillPoint(aDiff); + thePoint->ComputeDifference(aDiff); + } + delete aDiff; +} + +//======================================================================= +//function : Perform +//purpose : +//======================================================================= +Standard_Boolean GEOMImpl_Fillet1d::Perform(const Standard_Real theRadius) +{ + myDegreeOfRecursion = 0; + myResultParams.Clear(); + myResultOrientation.Clear(); + + Standard_Integer aNBSteps = 100; + Geom2dAdaptor_Curve aGAC(myCurve1); + switch (aGAC.GetType()) + { + case GeomAbs_Line: + aNBSteps = 2; + break; + case GeomAbs_Circle: + aNBSteps = 4; + break; + case GeomAbs_Ellipse: + aNBSteps = 5; + break; + case GeomAbs_BezierCurve: + case GeomAbs_BSplineCurve: + aNBSteps = 2 + aGAC.Degree() * aGAC.NbPoles(); + break; + default: // unknown: maximum + aNBSteps = 100; + } + + myRadius = theRadius; + + // Compute the intervals. + const Standard_Real aTol = Precision::Confusion(); + Geom2dAPI_InterCurveCurve anAPIInter(myCurve1, myCurve2, aTol); + const Geom2dInt_GInter &anInter = anAPIInter.Intersector(); + Standard_Integer aNb = anInter.NbPoints(); + Standard_Integer i; + TColStd_ListOfReal aParams; + TColStd_ListIteratorOfListOfReal anIter; + + // Treat intersection points. + for(i = 1; i <= aNb; i++) { + const IntRes2d_IntersectionPoint &aPoint = anInter.Point(i); + Standard_Real aParam = aPoint.ParamOnFirst(); + + // Adjust parameter on periodic curve. + if (myCurve1->IsPeriodic()) { + aParam = ElCLib::InPeriod + (aParam, myStart1, myStart1 + myCurve1->Period()); + } + + if (aParam > myStart1 + aTol && aParam < myEnd1 - aTol) { + // Add the point in the list in increasing order. + for(anIter.Initialize(aParams); anIter.More(); anIter.Next()) { + if (anIter.Value() > aParam) { + aParams.InsertBefore(aParam, anIter); + break; + } + } + + if (!anIter.More()) { + aParams.Append(aParam); + } + } + } + + // Treat intersection segments. + aNb = anInter.NbSegments(); + + for(i = 1; i <= aNb; i++) { + const IntRes2d_IntersectionSegment &aSegment = anInter.Segment(i); + + if (aSegment.HasFirstPoint() && aSegment.HasLastPoint()) { + Standard_Real aParam1 = aSegment.FirstPoint().ParamOnFirst(); + Standard_Real aParam2 = aSegment.LastPoint().ParamOnFirst(); + + // Adjust parameters on periodic curve. + if (myCurve1->IsPeriodic()) { + ElCLib::AdjustPeriodic(myStart1, myStart1 + myCurve1->Period(), + aTol, aParam1, aParam2); + } + + if (aParam1 > myStart1 + aTol && aParam1 < myEnd1 - aTol && + aParam2 > myStart1 + aTol && aParam2 < myEnd1 - aTol) { + // Add the point in the list in increasing order. + const Standard_Real aParam = 0.5*(aParam1 + aParam2); + + for(anIter.Initialize(aParams); anIter.More(); anIter.Next()) { + if (anIter.Value() > aParam) { + aParams.InsertBefore(aParam, anIter); + break; + } + } + + if (!anIter.More()) { + aParams.Append(aParam); + } + } + } + } + + // Add start and end parameters to the list. + aParams.Prepend(myStart1); + aParams.Append(myEnd1); + anIter.Initialize(aParams); + + // Perform each interval. + Standard_Real aStart = anIter.Value(); + + for (anIter.Next(); anIter.More(); anIter.Next()) { + const Standard_Real anEnd = anIter.Value(); + + // Perform the interval. + performInterval(aStart, anEnd, aNBSteps); + aStart = anEnd; + } + + if (myResultParams.Extent()) + return Standard_True; + + return Standard_False; +} + +//======================================================================= +//function : performInterval +//purpose : +//======================================================================= +void GEOMImpl_Fillet1d::performInterval(const Standard_Real theStart, + const Standard_Real theEnd, + const Standard_Integer theNBSteps) +{ + Standard_Real aParam, aStep, aDStep; + aStep = (theEnd - theStart) / theNBSteps; + aDStep = aStep/1000.; + + Standard_Integer aCycle; + for(aCycle = 2, myStartSide = Standard_False; aCycle; myStartSide = !myStartSide, aCycle--) + { + GEOMImpl_Fillet1dPoint *aLeft = NULL, *aRight = NULL; + + for(aParam = theStart + aStep; aParam < theEnd || fabs(theEnd - aParam) < Precision::Confusion(); aParam += aStep) + { + if (!aLeft) + { + aLeft = new GEOMImpl_Fillet1dPoint(aParam - aStep); + fillPoint(aLeft); + fillDiff(aLeft, aDStep, Standard_True); + } + + aRight = new GEOMImpl_Fillet1dPoint(aParam); + fillPoint(aRight); + fillDiff(aRight, aDStep, Standard_False); + + aLeft->FilterPoints(aRight); + performNewton(aLeft, aRight); + + delete aLeft; + aLeft = aRight; + } + delete aLeft; + } +} + +//======================================================================= +//function : processPoint +//purpose : +//======================================================================= +Standard_Boolean GEOMImpl_Fillet1d::processPoint(GEOMImpl_Fillet1dPoint* theLeft, + GEOMImpl_Fillet1dPoint* theRight, + Standard_Real theParameter) +{ + if (theParameter >= theLeft->GetParam() && theParameter < theRight->GetParam()) + { + Standard_Real aDX = theRight->GetParam() - theLeft->GetParam(); + if (theParameter - theLeft->GetParam() < aDX / 100.) + { + theParameter = theLeft->GetParam() + aDX / 100.; + } + if (theRight->GetParam() - theParameter < aDX / 100.) + { + theParameter = theRight->GetParam() - aDX / 100.; + } + + // Protection on infinite loop. + myDegreeOfRecursion++; + Standard_Real diffx = 0.001 * aDX; + if (myDegreeOfRecursion > 1000) + { + diffx *= 10.0; + if (myDegreeOfRecursion > 10000) + { + diffx *= 10.0; + if (myDegreeOfRecursion > 100000) + { + return Standard_True; + } + } + } + + GEOMImpl_Fillet1dPoint* aPoint1 = theLeft->Copy(); + GEOMImpl_Fillet1dPoint* aPoint2 = new GEOMImpl_Fillet1dPoint(theParameter); + fillPoint(aPoint2); + fillDiff(aPoint2, diffx, Standard_True); + + aPoint1->FilterPoints(aPoint2); + performNewton(aPoint1, aPoint2); + aPoint2->FilterPoints(theRight); + performNewton(aPoint2, theRight); + + delete aPoint1; + delete aPoint2; + return Standard_True; + } + + return Standard_False; +} + +//======================================================================= +//function : performNewton +//purpose : +//======================================================================= +void GEOMImpl_Fillet1d::performNewton(GEOMImpl_Fillet1dPoint* theLeft, + GEOMImpl_Fillet1dPoint* theRight) +{ + Standard_Integer a; + // check the left: if this is solution store it and remove it from the list of researching points of theLeft + a = theLeft->HasSolution(myRadius); + if (a) + { + if (theLeft->IsValid(a)) + { + myResultParams.Append(theLeft->GetParam()); + myResultOrientation.Append(myStartSide); + } + return; + } + + Standard_Real aDX = theRight->GetParam() - theLeft->GetParam(); + if ( aDX < Precision::Confusion() / 1000000.) + { + a = theRight->HasSolution(myRadius); + if (a) + if (theRight->IsValid(a)) + { + myResultParams.Append(theRight->GetParam()); + myResultOrientation.Append(myStartSide); + } + return; + } + + for(a = 1; a <= theLeft->GetNBValues(); a++) + { + Standard_Integer aNear = theLeft->GetNear(a); + + Standard_Real aA = (theRight->GetDiff(aNear) - theLeft->GetDiff(a)) / aDX; + Standard_Real aB = theLeft->GetDiff(a) - aA * theLeft->GetParam(); + Standard_Real aC = theLeft->GetValue(a) - theLeft->GetDiff(a) * theLeft->GetParam() + + aA * theLeft->GetParam() * theLeft->GetParam() / 2.0; + Standard_Real aDet = aB * aB - 2.0 * aA * aC; + + if ( fabs(aDet) < gp::Resolution() ) + continue; + + if (fabs(aA) < Precision::Confusion()) + { // linear case + if (fabs(aB) > 10e-20) + { + Standard_Real aX0 = - aC / aB; // use extremum + if (aX0 > theLeft->GetParam() && aX0 < theRight->GetParam()) + processPoint(theLeft, theRight, aX0); + } + else + { + processPoint(theLeft, theRight, theLeft->GetParam() + aDX / 2.0); // linear division otherwise + } + } + else + { + if (fabs(aB) > fabs(aDet * 1000000.)) + { // possible floating point operations accuracy errors + processPoint(theLeft, theRight, theLeft->GetParam() + aDX / 2.0); // linear division otherwise + } + else + { + if (aDet > 0) + { // two solutions + aDet = sqrt(aDet); + Standard_Boolean aRes = processPoint(theLeft, theRight, (- aB + aDet) / aA); + if (!aRes) + aRes = processPoint(theLeft, theRight, (- aB - aDet) / aA); + if (!aRes) + processPoint(theLeft, theRight, theLeft->GetParam() + aDX / 2.0); // linear division otherwise + } + else + { + Standard_Real aX0 = - aB / aA; // use extremum + if (aX0 > theLeft->GetParam() && aX0 < theRight->GetParam()) + processPoint(theLeft, theRight, aX0); + else + processPoint(theLeft, theRight, theLeft->GetParam() + aDX / 2.0); // linear division otherwise + } + } + } + } +} + +//======================================================================= +//function : Result +//purpose : +//======================================================================= +TopoDS_Edge GEOMImpl_Fillet1d::Result(const gp_Pnt& thePoint, + TopoDS_Edge& theEdge1, + TopoDS_Edge& theEdge2) +{ + TopoDS_Edge aResult; + gp_Pnt2d aTargetPoint2d; + Standard_Real aX, aY; + ElSLib::PlaneParameters(myPlane->Pln().Position(), thePoint, aX, aY); + aTargetPoint2d.SetCoord(aX, aY); + + // choose the nearest circle + Standard_Real aDistance, aP; + GEOMImpl_Fillet1dPoint *aNearest; + Standard_Integer a; + TColStd_ListIteratorOfListOfReal anIter(myResultParams); + for(aNearest = NULL, a = 1; anIter.More(); anIter.Next(), a++) + { + myStartSide = (myResultOrientation.Value(a)) ? Standard_True : Standard_False; + GEOMImpl_Fillet1dPoint *aPoint = new GEOMImpl_Fillet1dPoint(anIter.Value()); + fillPoint(aPoint); + if (!aPoint->HasSolution(myRadius)) + continue; + aP = fabs(aPoint->GetCenter().Distance(aTargetPoint2d) - myRadius); + if (!aNearest || aP < aDistance) + { + aNearest = aPoint; + aDistance = aP; + } + else + { + delete aPoint; + } + } + + if (!aNearest) + return aResult; + + // create circle edge + gp_Pnt aCenter = ElSLib::PlaneValue(aNearest->GetCenter().X(), + aNearest->GetCenter().Y(), + myPlane->Pln().Position()); + Handle(Geom_Circle) aCircle = + new Geom_Circle(gp_Ax2(aCenter, myPlane->Pln().Axis().Direction()), myRadius); + gp_Pnt2d aPoint2d1, aPoint2d2; + myCurve1->D0(aNearest->GetParam(), aPoint2d1); + myCurve2->D0(aNearest->GetParam2(), aPoint2d2); + gp_Pnt aPoint1 = ElSLib::PlaneValue(aPoint2d1.X(), aPoint2d1.Y(), myPlane->Pln().Position()); + gp_Pnt aPoint2 = ElSLib::PlaneValue(aPoint2d2.X(), aPoint2d2.Y(), myPlane->Pln().Position()); + + GeomAPI_ProjectPointOnCurve aProj(thePoint, aCircle); + Standard_Real aTarGetParam = aProj.LowerDistanceParameter(); + gp_Pnt aPointOnCircle = aProj.NearestPoint(); + + // Check extrema point manually, because there is a bug in Open CASCADE + // in calculation of nearest point to a circle near the parameter 0.0 + gp_Pnt p0 = ElCLib::Value(0.0, aCircle->Circ()); + if (p0.Distance(thePoint) < aPointOnCircle.Distance(thePoint)) + { + aTarGetParam = 0.0; + aPointOnCircle = p0; + } + + aProj.Perform(aPoint1); + Standard_Real aParam1 = aProj.LowerDistanceParameter(); + aProj.Perform(aPoint2); + Standard_Real aParam2 = aProj.LowerDistanceParameter(); + Standard_Boolean aIsOut = ((aParam1 < aTarGetParam && aParam2 < aTarGetParam) || + (aParam1 > aTarGetParam && aParam2 > aTarGetParam)); + if (aParam1 > aParam2) + aIsOut = !aIsOut; + BRepBuilderAPI_MakeEdge aBuilder(aCircle->Circ(), + aIsOut ? aParam2 : aParam1, + aIsOut? aParam1 : aParam2); + aResult = aBuilder.Edge(); + + // divide edges + Standard_Real aStart, anEnd; + Handle(Geom_Curve) aCurve = BRep_Tool::Curve(myEdge1, aStart, anEnd); + gp_Vec aDir; + aCurve->D1(aNearest->GetParam(), aPoint1, aDir); + + gp_Vec aCircleDir; + aCircle->D1(aParam1, aPoint1, aCircleDir); + if ((aCircleDir.Angle(aDir) > M_PI / 2.0) ^ aIsOut) + aStart = aNearest->GetParam(); + else + anEnd = aNearest->GetParam(); + + if (IsDivideEdge(myEdge1, aStart, anEnd)) + { + //Divide edge + BRepBuilderAPI_MakeEdge aDivider1(aCurve, aStart, anEnd); + if (myEdgesExchnged) + theEdge2 = aDivider1.Edge(); + else + theEdge1 = aDivider1.Edge(); + } + + aCurve = BRep_Tool::Curve(myEdge2, aStart, anEnd); + aCurve->D1(aNearest->GetParam2(), aPoint2, aDir); + + aCircle->D1(aParam2, aPoint2, aCircleDir); + if ((aCircleDir.Angle(aDir) > M_PI / 2.0) ^ (!aIsOut)) + aStart = aNearest->GetParam2(); + else + anEnd = aNearest->GetParam2(); + + if (IsDivideEdge(myEdge2, aStart, anEnd)) + { + BRepBuilderAPI_MakeEdge aDivider2(aCurve, aStart, anEnd); + if (myEdgesExchnged) + theEdge1 = aDivider2.Edge(); + else + theEdge2 = aDivider2.Edge(); + } + + delete aNearest; + return aResult; +} + +//======================================================================= +//function : AddValue +//purpose : +//======================================================================= +void GEOMImpl_Fillet1dPoint::AddValue(Standard_Real theValue, Standard_Boolean theValid) +{ + Standard_Integer a; + for(a = 1; a <= myV.Length(); a++) + { + if (theValue < myV.Value(a)) + { + myV.InsertBefore(a, theValue); + myValid.InsertBefore(a, (Standard_Integer)theValid); + return; + } + } + myV.Append(theValue); + myValid.Append((Standard_Integer)theValid); +} + +//======================================================================= +//function : ComputeDifference +//purpose : +//======================================================================= +Standard_Boolean GEOMImpl_Fillet1dPoint::ComputeDifference(GEOMImpl_Fillet1dPoint* thePoint) +{ + Standard_Integer a; + Standard_Boolean aDiffsSet = (myD.Length() != 0); + Standard_Real aDX = thePoint->GetParam() - myParam, aDY; + if (thePoint->myV.Length() == myV.Length()) + { // absolutely the same points + for(a = 1; a <= myV.Length(); a++) + { + aDY = thePoint->myV.Value(a) - myV.Value(a); + if ( aDiffsSet ) + myD.SetValue(a, fabs(aDX) > gp::Resolution() ? (aDY/aDX) : 0); + else + myD.Append( fabs(aDX) > gp::Resolution() ? (aDY/aDX) : 0); + } + return Standard_True; + } + // between the diffeerent points searching for nearest analogs + Standard_Integer b; + for(a = 1; a <= myV.Length(); a++) + { + for(b = 1; b <= thePoint->myV.Length(); b++) + { + if (b == 1 || fabs(thePoint->myV.Value(b) - myV.Value(a)) < fabs(aDY)) + aDY = thePoint->myV.Value(b) - myV.Value(a); + } + if (aDiffsSet) + { + if ( fabs(aDX) > gp::Resolution() && fabs(aDY / aDX) < fabs(myD.Value(a))) + myD.SetValue(a, aDY / aDX); + else + myD.SetValue(a, 0); + } + else + { + myD.Append( fabs(aDX) > gp::Resolution() ? aDY/aDX : 0); + } + } + + return Standard_False; +} + +//======================================================================= +//function : FilterPoints +//purpose : +//======================================================================= +void GEOMImpl_Fillet1dPoint::FilterPoints(GEOMImpl_Fillet1dPoint* thePoint) +{ + Standard_Integer a, b; + TColStd_SequenceOfReal aDiffs; + Standard_Real aY, aY2, aDX = thePoint->GetParam() - myParam; + for(a = 1; a <= myV.Length(); a++) + { + // searching for near point from thePoint + Standard_Integer aNear = 0; + Standard_Real aDiff = aDX * 10000.; + aY = myV.Value(a) + myD.Value(a) * aDX; + for(b = 1; b <= thePoint->myV.Length(); b++) + { + // calculate hypothesis value of the Y2 with the constant first and second derivative + aY2 = aY + aDX * (thePoint->myD.Value(b) - myD.Value(a)) / 2.0; + if (aNear == 0 || fabs(aY2 - thePoint->myV.Value(b)) < fabs(aDiff)) + { + aNear = b; + aDiff = aY2 - thePoint->myV.Value(b); + } + }//for b... + + if (aNear) + { + if (myV.Value(a) * thePoint->myV.Value(aNear) > 0) + {// the same sign at the same sides of the interval + if (myV.Value(a) * myD.Value(a) > 0) + { + if (fabs(myD.Value(a)) > Precision::Confusion()) + aNear = 0; + } + else + { + if (fabs(myV.Value(a)) > fabs(thePoint->myV.Value(aNear))) + if (thePoint->myV.Value(aNear) * thePoint->myD.Value(aNear) < 0 && + fabs(thePoint->myD.Value(aNear)) > Precision::Confusion()) + { + aNear = 0; + } + } + } + } + + if (aNear) + { + if (myV.Value(a) * thePoint->myV.Value(aNear) > 0) + { + if ((myV.Value(a) + myD.Value(a) * aDX) * myV.Value(a) > Precision::Confusion() && + (thePoint->myV.Value(aNear) + thePoint->myD.Value(aNear) * aDX) * thePoint->myV.Value(aNear) > Precision::Confusion()) + { + aNear = 0; + } + } + } + + if (aNear) + { + if ( fabs(aDX) < gp::Resolution() || fabs(aDiff / aDX) > 1.e+7) + { + aNear = 0; + } + } + + if (aNear == 0) + { // there is no near: remove it from the list + myV.Remove(a); + myD.Remove(a); + myValid.Remove(a); + a--; + } + else + { + Standard_Boolean aFound = Standard_False; + for(b = 1; b <= myNear.Length(); b++) + { + if (myNear.Value(b) == aNear) + { + if (fabs(aDiffs.Value(b)) < fabs(aDiff)) + { // return this 'near' + aFound = Standard_True; + myV.Remove(a); + myD.Remove(a); + myValid.Remove(a); + a--; + break; + } + else + { // remove the old 'near' + myV.Remove(b); + myD.Remove(b); + myValid.Remove(b); + myNear.Remove(b); + aDiffs.Remove(b); + a--; + break; + } + } + }//for b... + if (!aFound) + { + myNear.Append(aNear); + aDiffs.Append(aDiff); + } + } + }//for a... +} + +//======================================================================= +//function : Copy +//purpose : +//======================================================================= +GEOMImpl_Fillet1dPoint* GEOMImpl_Fillet1dPoint::Copy() +{ + GEOMImpl_Fillet1dPoint* aCopy = new GEOMImpl_Fillet1dPoint(myParam); + Standard_Integer a; + for(a = 1; a <= myV.Length(); a++) + { + aCopy->myV.Append(myV.Value(a)); + aCopy->myD.Append(myD.Value(a)); + aCopy->myValid.Append(myValid.Value(a)); + } + return aCopy; +} + +//======================================================================= +//function : HasSolution +//purpose : +//======================================================================= +Standard_Integer GEOMImpl_Fillet1dPoint::HasSolution(const Standard_Real theRadius) +{ + Standard_Integer a; + for(a = 1; a <= myV.Length(); a++) + { + if (fabs(sqrt(fabs(fabs(myV.Value(a)) + theRadius * theRadius)) - theRadius) < Precision::Confusion() / 10.) + return a; + } + return 0; +} + +//======================================================================= +//function : RemoveSolution +//purpose : +//======================================================================= +void GEOMImpl_Fillet1dPoint::RemoveSolution(Standard_Integer theIndex) +{ + myV.Remove(theIndex); + myD.Remove(theIndex); + myValid.Remove(theIndex); + myNear.Remove(theIndex); +} diff --git a/src/GeomAlgoImpl/GEOMImpl_Fillet1d.hxx b/src/GeomAlgoImpl/GEOMImpl_Fillet1d.hxx new file mode 100644 index 000000000..f49294f2c --- /dev/null +++ b/src/GeomAlgoImpl/GEOMImpl_Fillet1d.hxx @@ -0,0 +1,147 @@ +// Copyright (C) 2007-2020 CEA/DEN, EDF R&D, OPEN CASCADE +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : GEOMImpl_Fillet1d.hxx +// Module : GEOMImpl + +#ifndef _GEOMImpl_Fillet1d_HeaderFile +#define _GEOMImpl_Fillet1d_HeaderFile + +#include + +#include +#include + +#include + +#include +#include +#include + +class GEOMImpl_Fillet1dPoint; + +/** +* GEOMImpl_Fillet1d is 1D fillet algorithm on two planar edges with given radius +*/ + +class GEOMImpl_Fillet1d +{ +public: + //! Constructor + //! The fillet 1D algorithm is initialised by two edges and plane + Standard_EXPORT GEOMImpl_Fillet1d(const TopoDS_Edge& theEdge1, + const TopoDS_Edge& theEdge2, + const gp_Pln& thePlane); + //! Makes fillet with given radius + //! @returns Standard_True, if at least one result computed + Standard_EXPORT Standard_Boolean Perform(const Standard_Real theRadius); + + //! Returns result fillet edge and modified edges as out parameters + Standard_EXPORT TopoDS_Edge Result(const gp_Pnt& thePoint, TopoDS_Edge& theEdge1, TopoDS_Edge& theEdge2); + +private: + //! private methods + void performInterval(const Standard_Real theStart, + const Standard_Real theEnd, + const Standard_Integer theNBSteps); + void fillPoint(GEOMImpl_Fillet1dPoint*); + void fillDiff(GEOMImpl_Fillet1dPoint*, Standard_Real, Standard_Boolean); + void performNewton(GEOMImpl_Fillet1dPoint*, GEOMImpl_Fillet1dPoint*); + Standard_Boolean processPoint(GEOMImpl_Fillet1dPoint*, GEOMImpl_Fillet1dPoint*, Standard_Real); + +private: + //! private fields + TopoDS_Edge myEdge1, myEdge2; + Handle(Geom_Plane) myPlane; + Handle(Geom2d_Curve) myCurve1, myCurve2; + Standard_Real myStart1, myEnd1, myStart2, myEnd2, myRadius; + TColStd_ListOfReal myResultParams; + TColStd_SequenceOfInteger myResultOrientation; + Standard_Boolean myStartSide, myEdgesExchnged; + Standard_Integer myDegreeOfRecursion; +}; + + +/** +* GEOMImpl_Fillet1dPoint is an internal class for 1D fillet algorithm +* to store and compare computed solutions on edges +*/ + +class GEOMImpl_Fillet1dPoint +{ +public: + //! Puiblic methods + + //! Constructor + Standard_EXPORT GEOMImpl_Fillet1dPoint(Standard_Real theParam) + {myParam = theParam;} + + //! Make copy of point + //!WARNING: Copies only field values: myParam, myV, myD, myValid + Standard_EXPORT GEOMImpl_Fillet1dPoint* Copy(); // warning: this is not the full copy! + + //! Set/Get parameter + Standard_EXPORT inline void SetParam(Standard_Real theParam) + {myParam = theParam;} + Standard_EXPORT inline Standard_Real GetParam() const + {return myParam;} + Standard_EXPORT inline void SetParam2(const Standard_Real theParam2) + {myParam2 = theParam2;} + Standard_EXPORT inline Standard_Real GetParam2() + { return myParam2 ; } + + //! Returns validity + Standard_EXPORT inline Standard_Boolean IsValid(int theIndex) + {return (Standard_Boolean)myValid.Value(theIndex);} + + //! Get values + Standard_EXPORT inline Standard_Integer GetNBValues() {return myV.Length();} + Standard_EXPORT inline Standard_Real GetValue(Standard_Integer theIndex) + {return myV.Value(theIndex);} + Standard_EXPORT inline Standard_Real GetDiff(Standard_Integer theIndex) + {return myD.Value(theIndex);} + Standard_EXPORT inline Standard_Integer GetNear(Standard_Integer theIndex) + {return myNear.Value(theIndex);} + + //! Set/Get center point + Standard_EXPORT inline void SetCenter(const gp_Pnt2d thePoint) + {myCenter = thePoint;} + Standard_EXPORT inline const gp_Pnt2d GetCenter() + {return myCenter;} + + Standard_EXPORT void AddValue(Standard_Real theValue, Standard_Boolean theIsValid); + + //! compute difference between this and given point + Standard_EXPORT Standard_Boolean ComputeDifference(GEOMImpl_Fillet1dPoint*); + Standard_EXPORT void FilterPoints(GEOMImpl_Fillet1dPoint*); + + //! Checks if point contains solution and returns the index of it if any + Standard_EXPORT Standard_Integer HasSolution(Standard_Real theRadius); + //! Remove solution by index + void RemoveSolution(Standard_Integer theIndex); + +private: + //! Private fields + gp_Pnt2d myCenter; + Standard_Real myParam, myParam2; + TColStd_SequenceOfReal myV, myD; + TColStd_SequenceOfInteger myValid, myNear; +}; + +#endif diff --git a/src/ModelAPI/ModelAPI_Events.cpp b/src/ModelAPI/ModelAPI_Events.cpp index f616eb290..19e664f81 100644 --- a/src/ModelAPI/ModelAPI_Events.cpp +++ b/src/ModelAPI/ModelAPI_Events.cpp @@ -21,6 +21,7 @@ #include #include +#include //#define DEBUG_OBJECT_MOVED_MESSAGE #ifdef DEBUG_OBJECT_MOVED_MESSAGE @@ -384,3 +385,23 @@ void ModelAPI_ObjectMovedMessage::setCurrentPosition( << myCurrentPosition->y() - myOriginalPosition->y() << std::endl; #endif } + + +// ===== ModelAPI_Fillet1DFailedMessage ===== +ModelAPI_Fillet1DFailedMessage::ModelAPI_Fillet1DFailedMessage(const Events_ID theID, + const void* theSender) + : Events_Message(theID, theSender) +{} + +ModelAPI_Fillet1DFailedMessage::~ModelAPI_Fillet1DFailedMessage() +{} + +void ModelAPI_Fillet1DFailedMessage::setVertices(const ListOfShape& theVertices) +{ + myVertices = theVertices; +} + +const ListOfShape& ModelAPI_Fillet1DFailedMessage::vertices() const +{ + return myVertices; +} diff --git a/src/ModelAPI/ModelAPI_Events.h b/src/ModelAPI/ModelAPI_Events.h index c79ec94d0..2a63ab671 100644 --- a/src/ModelAPI/ModelAPI_Events.h +++ b/src/ModelAPI/ModelAPI_Events.h @@ -36,6 +36,7 @@ class ModelAPI_Document; class ModelAPI_ResultParameter; class GeomAPI_Pnt2d; +class GeomAPI_Shape; #if defined __GNUC__ || defined __clang__ #define MAYBE_UNUSED __attribute__((unused)) @@ -119,6 +120,9 @@ MAYBE_UNUSED static const char * EVENT_DOF_OBJECTS = "DoFObjects"; MAYBE_UNUSED static const char * EVENT_VISUAL_ATTRIBUTES = "UpdateVisualAttributes"; +/// Event ID that 1D-fillet failed (comes with ModelAPI_Fillet1DFailedMessage) +static const char * EVENT_1DFILLET_FAILED = "1DFilletFailed"; + /// Message that feature was changed (used for Object Browser update): moved, updated and deleted class MODELAPI_EXPORT ModelAPI_ObjectUpdatedMessage : public Events_MessageGroup { @@ -540,4 +544,27 @@ public: { return myCurrentPosition; } }; +/// Message that sends the failed vertices of 1D-fillet to highlight them in 3D viewer +class ModelAPI_Fillet1DFailedMessage : public Events_Message +{ +public: + /// Creates an message + MODELAPI_EXPORT ModelAPI_Fillet1DFailedMessage(const Events_ID theID, const void* theSender = 0); + /// Default destructor + MODELAPI_EXPORT virtual ~ModelAPI_Fillet1DFailedMessage(); + /// Static. Returns EventID of the message. + MODELAPI_EXPORT static Events_ID eventId() + { + return Events_Loop::eventByName(EVENT_1DFILLET_FAILED); + } + + /// Sets list of failed vertices + MODELAPI_EXPORT void setVertices(const std::list< std::shared_ptr >& theVertices); + /// Returns list of failed vertices + MODELAPI_EXPORT const std::list< std::shared_ptr >& vertices() const; + +private: + std::list< std::shared_ptr > myVertices; +}; + #endif diff --git a/src/ModelHighAPI/ModelHighAPI_Macro.h b/src/ModelHighAPI/ModelHighAPI_Macro.h index accf0c79f..333de514e 100644 --- a/src/ModelHighAPI/ModelHighAPI_Macro.h +++ b/src/ModelHighAPI/ModelHighAPI_Macro.h @@ -50,7 +50,7 @@ // Used in INTERFACE_N for create variable and getter #define DEFINE_ATTRIBUTE(NAME, TYPE, COMMENT) \ COMMENT \ - std::shared_ptr NAME() const { return VAR_NAME(NAME); } \ + virtual std::shared_ptr NAME() const { return VAR_NAME(NAME); } \ protected: \ std::shared_ptr VAR_NAME(NAME); \ public: -- 2.39.2