]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
Merge branch 'V9_5_BR'
authorvsr <vsr@opencascade.com>
Fri, 10 Jul 2020 16:24:56 +0000 (19:24 +0300)
committervsr <vsr@opencascade.com>
Fri, 10 Jul 2020 16:24:56 +0000 (19:24 +0300)
176 files changed:
doc/gui/General/Introduction.rst
src/BuildPlugin/BuildPlugin_Wire.cpp
src/FeaturesAPI/FeaturesAPI.i
src/FeaturesAPI/FeaturesAPI_Fillet.cpp
src/FeaturesAPI/FeaturesAPI_Fillet.h
src/FeaturesPlugin/CMakeLists.txt
src/FeaturesPlugin/FeaturesPlugin_Fillet1D.cpp [new file with mode: 0644]
src/FeaturesPlugin/FeaturesPlugin_Fillet1D.h [new file with mode: 0644]
src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp
src/FeaturesPlugin/FeaturesPlugin_Validators.cpp
src/FeaturesPlugin/FeaturesPlugin_Validators.h
src/FeaturesPlugin/FeaturesPlugin_msg_en.ts
src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts
src/FeaturesPlugin/FeaturesPlugin_msg_ru.ts
src/FeaturesPlugin/Test/TestFillet1D_ErrorMsg.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_1.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_2.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_3.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_4.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_5.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_6.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_7.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_8.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Vertices_9.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Wire_1.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Wire_2.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Wire_3.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Wire_4.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestFillet1D_Wire_5.py [new file with mode: 0644]
src/FeaturesPlugin/doc/FeaturesPlugin.rst
src/FeaturesPlugin/doc/TUI_fillet1DVertices.rst [new file with mode: 0644]
src/FeaturesPlugin/doc/TUI_fillet1DWire.rst [new file with mode: 0644]
src/FeaturesPlugin/doc/examples/fillet1d_vertices.py [new file with mode: 0644]
src/FeaturesPlugin/doc/examples/fillet1d_wire.py [new file with mode: 0644]
src/FeaturesPlugin/doc/fillet1dFeature.rst [new file with mode: 0644]
src/FeaturesPlugin/doc/images/Fillet1DPanel_Vertices.png [new file with mode: 0644]
src/FeaturesPlugin/doc/images/Fillet1DPanel_Wire.png [new file with mode: 0644]
src/FeaturesPlugin/doc/images/Fillet1DResult_Vertices.png [new file with mode: 0644]
src/FeaturesPlugin/doc/images/Fillet1DResult_Wire.png [new file with mode: 0644]
src/FeaturesPlugin/doc/images/fillet1d.png [new file with mode: 0644]
src/FeaturesPlugin/doc/images/fillet1d_points.png [new file with mode: 0644]
src/FeaturesPlugin/doc/images/fillet1d_wire.png [new file with mode: 0644]
src/FeaturesPlugin/fillet1d_widget.xml [new file with mode: 0644]
src/FeaturesPlugin/icons/fillet1d.png [new file with mode: 0644]
src/FeaturesPlugin/icons/fillet1d_points.png [new file with mode: 0644]
src/FeaturesPlugin/icons/fillet1d_wire.png [new file with mode: 0644]
src/FeaturesPlugin/plugin-Features.xml
src/FiltersPlugin/FiltersPlugin_OppositeToEdge.cpp
src/GeomAPI/GeomAPI_BSpline.cpp
src/GeomAPI/GeomAPI_BSpline.h
src/GeomAPI/GeomAPI_Edge.cpp
src/GeomAPI/GeomAPI_Edge.h
src/GeomAPI/GeomAPI_WireExplorer.cpp
src/GeomAPI/GeomAPI_WireExplorer.h
src/GeomAlgoAPI/CMakeLists.txt
src/GeomAlgoAPI/GeomAlgoAPI.i
src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.cpp
src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.h
src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp [new file with mode: 0644]
src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h [new file with mode: 0644]
src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp
src/GeomAlgoAPI/GeomAlgoAPI_Offset.h
src/GeomAlgoAPI/GeomAlgoAPI_Projection.cpp
src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.cpp
src/GeomAlgoAPI/GeomAlgoAPI_ShapeTools.h
src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.cpp
src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.h
src/GeomAlgoImpl/CMakeLists.txt
src/GeomAlgoImpl/GEOMImpl_Fillet1d.cxx [new file with mode: 0644]
src/GeomAlgoImpl/GEOMImpl_Fillet1d.hxx [new file with mode: 0644]
src/Model/Model_AttributeIntArray.cpp
src/Model/Model_AttributeSelection.h
src/ModelAPI/ModelAPI_Events.cpp
src/ModelAPI/ModelAPI_Events.h
src/ModelAPI/ModelAPI_Feature.h
src/ModelAPI/Test/Test19019_2.py
src/ModelAPI/Test/Test19058.py
src/ModelAPI/Test/Test2491.py
src/ModelHighAPI/ModelHighAPI.i
src/ModelHighAPI/ModelHighAPI_Dumper.cpp
src/ModelHighAPI/ModelHighAPI_Dumper.h
src/ModelHighAPI/ModelHighAPI_FeatureStore.cpp
src/ModelHighAPI/ModelHighAPI_Macro.h
src/ModelHighAPI/ModelHighAPI_Tools.cpp
src/ModuleBase/ModuleBase_PagedContainer.cpp
src/ModuleBase/ModuleBase_WidgetOptionalBox.cpp
src/ModuleBase/ModuleBase_WidgetOptionalBox.h
src/ModuleBase/ModuleBase_WidgetRadiobox.cpp
src/ModuleBase/ModuleBase_WidgetRadiobox.h
src/PartSet/PartSet_CustomPrs.cpp
src/PartSet/PartSet_CustomPrs.h
src/PartSet/PartSet_Module.cpp
src/PartSet/PartSet_OperationPrs.cpp
src/PartSet/PartSet_SketcherMgr.cpp
src/PartSet/PartSet_SketcherReentrantMgr.h
src/PartSet/PartSet_TreeNodes.cpp
src/PartSet/PartSet_TreeNodes.h
src/PartSet/PartSet_WidgetSketchLabel.cpp
src/PartSet/PartSet_WidgetSketchLabel.h
src/PythonAPI/model/dump/DumpAssistant.py
src/SketchAPI/CMakeLists.txt
src/SketchAPI/SketchAPI.i
src/SketchAPI/SketchAPI_Constraint.cpp
src/SketchAPI/SketchAPI_ConstraintAngle.cpp
src/SketchAPI/SketchAPI_Offset.cpp [new file with mode: 0644]
src/SketchAPI/SketchAPI_Offset.h [new file with mode: 0644]
src/SketchAPI/SketchAPI_Projection.cpp
src/SketchAPI/SketchAPI_Projection.h
src/SketchAPI/SketchAPI_Sketch.cpp
src/SketchAPI/SketchAPI_Sketch.h
src/SketchAPI/SketchAPI_swig.h
src/SketchAPI/Test/TestSketch.py
src/SketchPlugin/CMakeLists.txt
src/SketchPlugin/SketchPlugin_CurveFitting.cpp [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_CurveFitting.h [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_Ellipse.cpp
src/SketchPlugin/SketchPlugin_MacroBSpline.cpp
src/SketchPlugin/SketchPlugin_MacroBSpline.h
src/SketchPlugin/SketchPlugin_Offset.cpp [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_Offset.h [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_Plugin.cpp
src/SketchPlugin/SketchPlugin_Projection.cpp
src/SketchPlugin/SketchPlugin_Projection.h
src/SketchPlugin/SketchPlugin_SketchEntity.cpp
src/SketchPlugin/SketchPlugin_SketchEntity.h
src/SketchPlugin/SketchPlugin_Tools.cpp
src/SketchPlugin/SketchPlugin_Tools.h
src/SketchPlugin/SketchPlugin_Validators.cpp
src/SketchPlugin/SketchPlugin_Validators.h
src/SketchPlugin/SketchPlugin_msg_en.ts
src/SketchPlugin/SketchPlugin_msg_fr.ts
src/SketchPlugin/Test/TestCurveFitting1.py [new file with mode: 0644]
src/SketchPlugin/Test/TestCurveFitting2.py [new file with mode: 0644]
src/SketchPlugin/Test/TestCurveFitting3.py [new file with mode: 0644]
src/SketchPlugin/Test/TestCurveFitting4.py [new file with mode: 0644]
src/SketchPlugin/Test/TestOffset1.py [new file with mode: 0644]
src/SketchPlugin/Test/TestOffset2.py [new file with mode: 0644]
src/SketchPlugin/Test/TestProjectionEllipse.py
src/SketchPlugin/Test/TestProjectionWithoutReference.py [new file with mode: 0644]
src/SketchPlugin/doc/SketchPlugin.rst
src/SketchPlugin/doc/TUI_curvefitting.rst [new file with mode: 0644]
src/SketchPlugin/doc/TUI_offset.rst [new file with mode: 0644]
src/SketchPlugin/doc/curveFittingFeature.rst [new file with mode: 0644]
src/SketchPlugin/doc/examples/approximation.py [new file with mode: 0644]
src/SketchPlugin/doc/examples/interpolation.py [new file with mode: 0644]
src/SketchPlugin/doc/examples/offset.py [new file with mode: 0644]
src/SketchPlugin/doc/images/Offset_panel.png [new file with mode: 0644]
src/SketchPlugin/doc/images/Offset_res.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_approximation_res.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_interpolation_res.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_panel_approximation.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png [new file with mode: 0644]
src/SketchPlugin/doc/images/offset.png [new file with mode: 0644]
src/SketchPlugin/doc/offsetFeature.rst [new file with mode: 0644]
src/SketchPlugin/icons/curvefitting.png [new file with mode: 0644]
src/SketchPlugin/icons/offset.png [new file with mode: 0644]
src/SketchPlugin/plugin-Sketch.xml
src/SketchSolver/CMakeLists.txt
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp
src/SketchSolver/SketchSolver_ConstraintOffset.cpp [new file with mode: 0644]
src/SketchSolver/SketchSolver_ConstraintOffset.h [new file with mode: 0644]
src/SketcherPrs/CMakeLists.txt
src/SketcherPrs/SketcherPrs_Factory.cpp
src/SketcherPrs/SketcherPrs_Factory.h
src/SketcherPrs/SketcherPrs_Offset.cpp [new file with mode: 0644]
src/SketcherPrs/SketcherPrs_Offset.h [new file with mode: 0644]
src/SketcherPrs/SketcherPrs_Tools.cpp
src/SketcherPrs/icons/offset.png [new file with mode: 0644]
src/XGUI/XGUI_ContextMenuMgr.cpp
src/XGUI/XGUI_OperationMgr.cpp
src/XGUI/XGUI_OperationMgr.h
src/XGUI/XGUI_Workshop.cpp

index fe03d3c85079bb649f24de4c56e25befb671aca0..d11c9b6a940caaf030986a360d97f84742e97218 100644 (file)
@@ -209,7 +209,7 @@ Note, that the result used in feature as argument is removed.
 
 This child result can be restored using **Recover** feature.
 
-Each feature, result, construction, group, field, parameter can be renamed using *Rename* pop-up menu command.
+Each feature, result, construction, group, field, parameter can be renamed using *Rename* pop-up menu command (hotkey "F2").
 
 .. figure:: /images/popup_menu_object_browser_feature.png
    :align: center
index 124a03abb68051ccad3c3d8ee105f8d2609af01e..f3d48bd8b4daeaf12131ee702fa40b4256bf9a92 100644 (file)
@@ -122,7 +122,10 @@ void BuildPlugin_Wire::execute()
       for (ListOfShape::const_iterator anIt = anEdges.cbegin(); anIt != anEdges.cend(); ++anIt) {
         std::shared_ptr<GeomAPI_Edge> 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;
         }
       }
index 3e10651756804744b07c9acdac17220c1ba866ea..d98babe2e23f57ebf8989fc337c79dda176f4879 100644 (file)
@@ -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)
index c9b446b6b1121f14310ddb4cb4e0a696aeb0de5f..332b5820878c9eb63c6d4ae8dddb8db63edaf8e2 100644 (file)
 
 #include <ModelHighAPI_Double.h>
 #include <ModelHighAPI_Dumper.h>
-////#include <ModelHighAPI_Reference.h>
+#include <ModelHighAPI_Selection.h>
 #include <ModelHighAPI_Tools.h>
 
+static GeomAPI_Shape::ShapeType typeOfSelection(
+    const std::list<ModelHighAPI_Selection>& theBaseObjects)
+{
+  std::string aType = theBaseObjects.empty() ? "SHAPE" : theBaseObjects.front().shapeType();
+  return GeomAPI_Shape::shapeTypeByStr(aType);
+}
+
+//==================================================================================================
+
 FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr<ModelAPI_Feature>& theFeature)
   : ModelHighAPI_Interface(theFeature)
+{
+}
+
+//==================================================================================================
+
+FeaturesAPI_Fillet1D::FeaturesAPI_Fillet1D(const std::shared_ptr<ModelAPI_Feature>& theFeature)
+  : FeaturesAPI_Fillet(theFeature)
 {
   initialize();
 }
 
-FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr<ModelAPI_Feature>& theFeature,
-                                       const std::list<ModelHighAPI_Selection>& theBaseObjects,
-                                       const ModelHighAPI_Double& theRadius)
-  : ModelHighAPI_Interface(theFeature)
+FeaturesAPI_Fillet1D::FeaturesAPI_Fillet1D(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                           const std::list<ModelHighAPI_Selection>& 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<ModelHighAPI_Selection>& 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<ModelAPI_Feature>& theFeature)
+  : FeaturesAPI_Fillet(theFeature)
+{
+  initialize();
+}
+
+FeaturesAPI_Fillet2D::FeaturesAPI_Fillet2D(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                           const std::list<ModelHighAPI_Selection>& 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<ModelAPI_Feature>&
   }
 }
 
-FeaturesAPI_Fillet::FeaturesAPI_Fillet(const std::shared_ptr<ModelAPI_Feature>& theFeature,
-                                       const std::list<ModelHighAPI_Selection>& theBaseObjects,
-                                       const ModelHighAPI_Double& theRadius1,
-                                       const ModelHighAPI_Double& theRadius2)
-  : ModelHighAPI_Interface(theFeature)
+FeaturesAPI_Fillet2D::FeaturesAPI_Fillet2D(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                           const std::list<ModelHighAPI_Selection>& 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<ModelAPI_Feature>&
   }
 }
 
-FeaturesAPI_Fillet::~FeaturesAPI_Fillet()
+FeaturesAPI_Fillet2D::~FeaturesAPI_Fillet2D()
 {
 }
 
-//==================================================================================================
-void FeaturesAPI_Fillet::setBase(const std::list<ModelHighAPI_Selection>& theBaseObjects)
+void FeaturesAPI_Fillet2D::setBase(const std::list<ModelHighAPI_Selection>& theBaseObjects)
 {
   mybaseObjects->clear();
   fillAttribute(theBaseObjects, mybaseObjects);
@@ -73,7 +164,7 @@ void FeaturesAPI_Fillet::setBase(const std::list<ModelHighAPI_Selection>& 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<ModelAPI_Document>& thePart,
                     const ModelHighAPI_Double& theRadius2,
                     const bool keepSubResults)
 {
-  std::shared_ptr<ModelAPI_Feature> 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;
 }
index 37eeef6884d1cc10d9a8833eb33cd3eea5641a6b..7eac5d6c882c3bfd2f71a69bea4e4e62f8a6b48c 100644 (file)
@@ -23,6 +23,7 @@
 #include "FeaturesAPI.h"
 
 #include <FeaturesPlugin_Fillet.h>
+#include <FeaturesPlugin_Fillet1D.h>
 
 #include <ModelHighAPI_Double.h>
 #include <ModelHighAPI_Interface.h>
@@ -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<ModelAPI_AttributeDouble> radius() const = 0;
+
+  /// Modify base objects of the fillet.
+  virtual void setBase(const std::list<ModelHighAPI_Selection>& theBaseObjects) = 0;
+
+  /// Modify fillet to have fixed radius
+  virtual void setRadius(const ModelHighAPI_Double& theRadius) = 0;
+
+protected:
+  FeaturesAPI_Fillet(const std::shared_ptr<ModelAPI_Feature>& theFeature);
+};
+
+/// Pointer on the fillet object.
+typedef std::shared_ptr<FeaturesAPI_Fillet> 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<ModelAPI_Feature>& theFeature);
+  explicit FeaturesAPI_Fillet1D(const std::shared_ptr<ModelAPI_Feature>& theFeature);
 
   /// Constructor with values.
   FEATURESAPI_EXPORT
-  explicit FeaturesAPI_Fillet(const std::shared_ptr<ModelAPI_Feature>& theFeature,
-                              const std::list<ModelHighAPI_Selection>& theBaseObjects,
-                              const ModelHighAPI_Double& theRadius);
+  explicit FeaturesAPI_Fillet1D(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                const std::list<ModelHighAPI_Selection>& 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<ModelHighAPI_Selection>& 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<ModelAPI_Feature>& theFeature);
+
+  /// Constructor with values.
+  FEATURESAPI_EXPORT
+  explicit FeaturesAPI_Fillet2D(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                const std::list<ModelHighAPI_Selection>& theBaseObjects,
+                                const ModelHighAPI_Double& theRadius);
 
   /// Constructor with values.
   FEATURESAPI_EXPORT
-  explicit FeaturesAPI_Fillet(const std::shared_ptr<ModelAPI_Feature>& theFeature,
-                              const std::list<ModelHighAPI_Selection>& theBaseObjects,
-                              const ModelHighAPI_Double& theRadius1,
-                              const ModelHighAPI_Double& theRadius2);
+  explicit FeaturesAPI_Fillet2D(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                const std::list<ModelHighAPI_Selection>& 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<ModelHighAPI_Selection>& theBaseObjects);
+  virtual void setBase(const std::list<ModelHighAPI_Selection>& 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<FeaturesAPI_Fillet> FilletPtr;
 
 /// \ingroup CPPHighAPI
 /// \brief Create Fillet feature.
index 1801536207d4688af539af76427628c5eea5b48b..0169cd9e3bea23cb95db9b87af56cfd3eb622f88 100644 (file)
@@ -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
@@ -664,4 +667,19 @@ ADD_UNIT_TESTS(TestExtrusion.py
                Test19066.py
                Test19115.py
                Test19196.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 (file)
index 0000000..c19cfa3
--- /dev/null
@@ -0,0 +1,201 @@
+// 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 <FeaturesPlugin_Fillet1D.h>
+#include <FeaturesPlugin_Tools.h>
+
+#include <GeomAlgoAPI_Fillet1D.h>
+#include <GeomAlgoAPI_MapShapesAndAncestors.h>
+#include <GeomAlgoAPI_ShapeTools.h>
+#include <GeomAlgoAPI_Tools.h>
+
+#include <GeomAPI_Edge.h>
+#include <GeomAPI_Pln.h>
+#include <GeomAPI_WireExplorer.h>
+
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeSelectionList.h>
+#include <ModelAPI_AttributeString.h>
+#include <ModelAPI_Events.h>
+
+void sendMessageWithFailedShapes(const ListOfShape& theVertices)
+{
+  std::shared_ptr<ModelAPI_ShapesFailedMessage> aMessage(
+      new ModelAPI_ShapesFailedMessage(Events_Loop::eventByName(EVENT_OPERATION_SHAPES_FAILED)));
+  aMessage->setShapes(theVertices);
+  Events_Loop::loop()->send(aMessage);
+}
+
+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);
+}
+
+void FeaturesPlugin_Fillet1D::attributeChanged(const std::string& theID)
+{
+  if (theID == CREATION_METHOD()) {
+    // creation method is changed, drop failed vertices and send the message
+    removeResults(0);
+    sendMessageWithFailedShapes(ListOfShape());
+  }
+}
+
+bool FeaturesPlugin_Fillet1D::baseShapes(ListOfShape& theWires, MapShapeSubs& theWireVertices)
+{
+  std::set<GeomShapePtr, GeomAPI_Shape::Comparator> 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<GeomShapePtr, GeomAPI_Shape::Comparator> 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<GeomAlgoAPI_Fillet1D> 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
+    sendMessageWithFailedShapes(myFailedVertices);
+  }
+
+  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 (file)
index 0000000..074d03c
--- /dev/null
@@ -0,0 +1,119 @@
+// 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 <FeaturesPlugin.h>
+
+#include <GeomAPI_Shape.h>
+#include <ModelAPI_Feature.h>
+
+#include <map>
+
+/// \class FeaturesPlugin_Fillet1D
+/// \ingroup Plugins
+/// \brief Feature for appling fillet on vertices of 3D wire.
+class FeaturesPlugin_Fillet1D : public ModelAPI_Feature
+{
+  typedef std::map<GeomShapePtr, ListOfShape, GeomAPI_Shape::Comparator> 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();
+
+  /// Called on change of any argument-attribute of this object
+  /// \param theID identifier of changed attribute
+  FEATURESPLUGIN_EXPORT virtual void attributeChanged(const std::string& theID);
+
+  /// 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
index 9cf7af9d90e56c02fbca6edfaf8821442fbe0c5f..0d49dfe08d1da9ca019628b9784e630e8b5331f9 100644 (file)
@@ -30,6 +30,7 @@
 #include <FeaturesPlugin_ExtrusionCut.h>
 #include <FeaturesPlugin_ExtrusionFuse.h>
 #include <FeaturesPlugin_Fillet.h>
+#include <FeaturesPlugin_Fillet1D.h>
 #include <FeaturesPlugin_Intersection.h>
 #include <FeaturesPlugin_Measurement.h>
 #include <FeaturesPlugin_MultiRotation.h>
@@ -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()) {
index e92148495868ded97bada247c73ca484abfb356b..e84cae4189a61710a5ec8d30776ba8474f5a03f7 100644 (file)
@@ -54,6 +54,7 @@
 #include <GeomAPI_ShapeIterator.h>
 
 #include <GeomAlgoAPI_CompoundBuilder.h>
+#include <GeomAlgoAPI_MapShapesAndAncestors.h>
 #include <GeomAlgoAPI_Prism.h>
 #include <GeomAlgoAPI_ShapeBuilder.h>
 #include <GeomAlgoAPI_ShapeTools.h>
@@ -941,6 +942,79 @@ bool FeaturesPlugin_ValidatorFilletSelection::isValid(const AttributePtr& theAtt
   return true;
 }
 
+
+//==================================================================================================
+bool FeaturesPlugin_ValidatorFillet1DSelection::isValid(const AttributePtr& theAttribute,
+                                                        const std::list<std::string>& theArguments,
+                                                        Events_InfoMessage& theError) const
+{
+  AttributeSelectionListPtr anAttrSelectionList =
+    std::dynamic_pointer_cast<ModelAPI_AttributeSelectionList>(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<GeomShapePtr, MapShapeToShapes> 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<GeomShapePtr, MapShapeToShapes>::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<std::string>& theArguments,
index 59eb5fd43929dc7124c0ee744a016ff77f3ff177..951d4c52f969eb17ce2924d31fe0b5732e43c242 100644 (file)
@@ -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<std::string>& theArguments,
+                       Events_InfoMessage& theError) const;
+};
+
 /// \class FeaturesPlugin_ValidatorPartitionSelection
 /// \ingroup Validators
 /// \brief Validates selection for partition.
index 0ff1004eeb8a3f1aba72d2007d64ce5fc19aac3f..c65a3e3ed60afab7f8f86b2885a7ba793b0c344f 100644 (file)
       <translation>Base shape is not selected.</translation>
     </message>
   </context>
+
+  <!-- 1D-fillet -->
+  <context>
+    <name>Fillet1D</name>
+    <message>
+      <source>1D-fillet</source>
+      <translation>1D-fillet</translation>
+    </message>
+    <message>
+      <source>Perform fillet on vertices of a wire</source>
+      <translation>Perform fillet on vertices of a wire</translation>
+    </message>
+    <message>
+      <source>Wire has no vertices for fillet.</source>
+      <translation>Wire has no vertices for fillet.</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_wires</name>
+    <message>
+      <source>Wires</source>
+      <translation>Wires</translation>
+    </message>
+    <message>
+      <source>Select wires</source>
+      <translation>Select wires</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Select wires.</translation>
+    </message>
+    <message>
+      <source>Fillet each sharp corner of the wire</source>
+      <translation>Fillet each sharp corner of the wire</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_vertices</name>
+    <message>
+      <source>Vertices</source>
+      <translation>Vertices</translation>
+    </message>
+    <message>
+      <source>Select vertices</source>
+      <translation>Select vertices</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Select vertices on wires.</translation>
+    </message>
+    <message>
+      <source>Fillet the specified corners of the wire</source>
+      <translation>Fillet the specified corners of the wire</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_vertices:FeaturesPlugin_ValidatorFillet1DSelection</name>
+    <message>
+      <source>Selected vertex is not a wire corner</source>
+      <translation>Selected vertex is not a wire corner</translation>
+    </message>
+    <message>
+      <source>Selected shape is not a vertex</source>
+      <translation>Selected shape is not a vertex</translation>
+    </message>
+    <message>
+      <source>Selected vertex does not exist in the wire</source>
+      <translation>Selected vertex does not exist in the wire</translation>
+    </message>
+    <message>
+      <source>Vertex should be shared between 2 edges exactly</source>
+      <translation>Vertex should be shared between 2 edges exactly</translation>
+    </message>
+    <message>
+      <source>Error: Edges are not planar</source>
+      <translation>Error: Edges are not planar</translation>
+    </message>
+    <message>
+      <source>Error: Edges are tangent</source>
+      <translation>Error: Edges are tangent</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:creation_method</name>
+    <message>
+      <source>Wires</source>
+      <translation>Wires</translation>
+    </message>
+    <message>
+      <source>Vertices</source>
+      <translation>Vertices</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:radius</name>
+    <message>
+      <source>Radius</source>
+      <translation>Radius</translation>
+    </message>
+    <message>
+      <source>Fillet radius</source>
+      <translation>Fillet radius</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Specify fillet radius.</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:radius:GeomValidators_Positive</name>
+    <message>
+      <source>Value is too small.</source>
+      <translation>Value is too small.</translation>
+    </message>
+  </context>
+
 </TS>
index 0dfcdfacfc2121fad06c2cb84f1583b09f5feeb1..6f821a2e2fa1c59eadfbf68ab80c4f2e9fbd427a 100644 (file)
       <source>Fillet</source>
       <translation>Congé</translation>
     </message>
+    <message>
+      <source>1D-fillet</source>
+      <translation>1D-congé</translation>
+    </message>
     <message>
       <source>Fuse</source>
       <translation>Fusionner</translation>
     </message>
   </context>
 
+  <!-- 1D-fillet -->
+  <context>
+    <name>Fillet1D</name>
+    <message>
+      <source>1D-fillet</source>
+      <translation>1D-congé</translation>
+    </message>
+    <message>
+      <source>Perform fillet on vertices of a wire</source>
+      <translation>Effectuer un congé sur les sommets d&apos;un contour</translation>
+    </message>
+    <message>
+      <source>Wire has no vertices for fillet.</source>
+      <translation>Le contour n&apos;a pas de sommet pour le congé.</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_wires</name>
+    <message>
+      <source>Wires</source>
+      <translation>Contours</translation>
+    </message>
+    <message>
+      <source>Select wires</source>
+      <translation>Sélectionnez les contours</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Sélectionnez les contours.</translation>
+    </message>
+    <message>
+      <source>Fillet each sharp corner of the wire</source>
+      <translation>Raccordez chaque coin pointu du contour</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_vertices</name>
+    <message>
+      <source>Vertices</source>
+      <translation>Sommets</translation>
+    </message>
+    <message>
+      <source>Select vertices</source>
+      <translation>Sélectionnez les sommets</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Sélectionnez des sommets sur les contours.</translation>
+    </message>
+    <message>
+      <source>Fillet the specified corners of the wire</source>
+      <translation>Raccorder les coins spécifiés du contour</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_vertices:FeaturesPlugin_ValidatorFillet1DSelection</name>
+    <message>
+      <source>Selected vertex is not a wire corner</source>
+      <translation>Le sommet sélectionné n&apos;est pas un coin de contour</translation>
+    </message>
+    <message>
+      <source>Selected shape is not a vertex</source>
+      <translation>La forme sélectionnée n&apos;est pas un sommet</translation>
+    </message>
+    <message>
+      <source>Selected vertex does not exist in the wire</source>
+      <translation>Le sommet sélectionné n&apos;existe pas dans le contour</translation>
+    </message>
+    <message>
+      <source>Vertex should be shared between 2 edges exactly</source>
+      <translation>Le sommet doit être partagé entre 2 bords exactement</translation>
+    </message>
+    <message>
+      <source>Error: Edges are not planar</source>
+      <translation>Erreur: les arêtes ne sont pas planes</translation>
+    </message>
+    <message>
+      <source>Error: Edges are tangent</source>
+      <translation>Erreur: les bords sont tangents</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:creation_method</name>
+    <message>
+      <source>Wires</source>
+      <translation>Contours</translation>
+    </message>
+    <message>
+      <source>Vertices</source>
+      <translation>Sommets</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:radius</name>
+    <message>
+      <source>Radius</source>
+      <translation>Rayon</translation>
+    </message>
+    <message>
+      <source>Fillet radius</source>
+      <translation>Rayon de congé</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Spécifiez le rayon du congé.</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:radius:GeomValidators_Positive</name>
+    <message>
+      <source>Value is too small.</source>
+      <translation>La valeur est trop petite.</translation>
+    </message>
+  </context>
+
   <!-- Fuse -->
   <context>
     <name>Fuse</name>
index 4e52f45275f6f58f31e0b04506eea85d44d97148..e2777069916eb3f9224efa0e75d3462a1a4b892b 100644 (file)
       <translation>Ошибка: не все выбранные объекты являются подэлементами твердых тел.</translation>
     </message>
   </context>
+
+  <!-- 1D-fillet -->
+  <context>
+    <name>Fillet1D</name>
+    <message>
+      <source>1D-fillet</source>
+      <translation>1D-сглаживание</translation>
+    </message>
+    <message>
+      <source>Perform fillet on vertices of a wire</source>
+      <translation>Выполнить сглаживание в узлах контура</translation>
+    </message>
+    <message>
+      <source>Wire has no vertices for fillet.</source>
+      <translation>Контур не имеет узлов, подходящих для сглаживания.</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_wires</name>
+    <message>
+      <source>Wires</source>
+      <translation>Контуры</translation>
+    </message>
+    <message>
+      <source>Select wires</source>
+      <translation>Выберите контуры</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Выберите контуры.</translation>
+    </message>
+    <message>
+      <source>Fillet each sharp corner of the wire</source>
+      <translation>Сгладить все острые углы контура</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_vertices</name>
+    <message>
+      <source>Vertices</source>
+      <translation>Узлы</translation>
+    </message>
+    <message>
+      <source>Select vertices</source>
+      <translation>Выберите узлы</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Выберите острые углы на контуре.</translation>
+    </message>
+    <message>
+      <source>Fillet the specified corners of the wire</source>
+      <translation>Сгладить выбранные узлы контура</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:main_vertices:FeaturesPlugin_ValidatorFillet1DSelection</name>
+    <message>
+      <source>Selected vertex is not a wire corner</source>
+      <translation>Выбранный узел не принадлежит контуру</translation>
+    </message>
+    <message>
+      <source>Selected shape is not a vertex</source>
+      <translation>Выбранный объект не является узлом</translation>
+    </message>
+    <message>
+      <source>Selected vertex does not exist in the wire</source>
+      <translation>Выбранный узел не принадлежит контуру</translation>
+    </message>
+    <message>
+      <source>Vertex should be shared between 2 edges exactly</source>
+      <translation>Узел должен примыкать ровно к двум рёбрам</translation>
+    </message>
+    <message>
+      <source>Error: Edges are not planar</source>
+      <translation>Ошибка: рёбра не лежат на одной плоскости</translation>
+    </message>
+    <message>
+      <source>Error: Edges are tangent</source>
+      <translation>Ошибка: рёбра касаются друг друга</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:creation_method</name>
+    <message>
+      <source>Wires</source>
+      <translation>Контуры</translation>
+    </message>
+    <message>
+      <source>Vertices</source>
+      <translation>Узлы</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:radius</name>
+    <message>
+      <source>Radius</source>
+      <translation>Радиус</translation>
+    </message>
+    <message>
+      <source>Fillet radius</source>
+      <translation>Радиус сглаживания</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Задайте радиус сглаживания.</translation>
+    </message>
+  </context>
+  <context>
+    <name>Fillet1D:radius:GeomValidators_Positive</name>
+    <message>
+      <source>Value is too small.</source>
+      <translation>Значение радиуса слишком мало.</translation>
+    </message>
+  </context>
+
 </TS>
diff --git a/src/FeaturesPlugin/Test/TestFillet1D_ErrorMsg.py b/src/FeaturesPlugin/Test/TestFillet1D_ErrorMsg.py
new file mode 100644 (file)
index 0000000..d491c37
--- /dev/null
@@ -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 (file)
index 0000000..078f495
--- /dev/null
@@ -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 (file)
index 0000000..295f226
--- /dev/null
@@ -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 (file)
index 0000000..2d1a78b
--- /dev/null
@@ -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 (file)
index 0000000..9112fcd
--- /dev/null
@@ -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 (file)
index 0000000..a240690
--- /dev/null
@@ -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 (file)
index 0000000..46ffa86
--- /dev/null
@@ -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 (file)
index 0000000..777ecc0
--- /dev/null
@@ -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 (file)
index 0000000..ac9bdcf
--- /dev/null
@@ -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 (file)
index 0000000..2305d0c
--- /dev/null
@@ -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 (file)
index 0000000..aaad4ec
--- /dev/null
@@ -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 (file)
index 0000000..dea6667
--- /dev/null
@@ -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 (file)
index 0000000..834dd2b
--- /dev/null
@@ -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 (file)
index 0000000..5249526
--- /dev/null
@@ -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 (file)
index 0000000..b05c0c6
--- /dev/null
@@ -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))
index 7f9ac37f7fccb0ecc72a39860f770b3e8a9bd108..fb8e2a344b707376d72df48edb52804796a2e7bf 100644 (file)
@@ -18,6 +18,7 @@ Features plug-in provides a set of common topological operations. It implements
    extrusionCutFeature.rst
    extrusionFeature.rst
    extrusionFuseFeature.rst
+   fillet1dFeature.rst
    filletFeature.rst
    fuseFeatureFaces.rst
    importResultFeature.rst
diff --git a/src/FeaturesPlugin/doc/TUI_fillet1DVertices.rst b/src/FeaturesPlugin/doc/TUI_fillet1DVertices.rst
new file mode 100644 (file)
index 0000000..6a7f037
--- /dev/null
@@ -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 <examples/fillet1d_vertices.py >` 
+   
diff --git a/src/FeaturesPlugin/doc/TUI_fillet1DWire.rst b/src/FeaturesPlugin/doc/TUI_fillet1DWire.rst
new file mode 100644 (file)
index 0000000..c222306
--- /dev/null
@@ -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 <examples/fillet1d_wire.py >` 
+   
diff --git a/src/FeaturesPlugin/doc/examples/fillet1d_vertices.py b/src/FeaturesPlugin/doc/examples/fillet1d_vertices.py
new file mode 100644 (file)
index 0000000..d2a0cbe
--- /dev/null
@@ -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 (file)
index 0000000..36ccfc9
--- /dev/null
@@ -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 (file)
index 0000000..e3a30a4
--- /dev/null
@@ -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 (file)
index 0000000..883c5e0
Binary files /dev/null and b/src/FeaturesPlugin/doc/images/Fillet1DPanel_Vertices.png differ
diff --git a/src/FeaturesPlugin/doc/images/Fillet1DPanel_Wire.png b/src/FeaturesPlugin/doc/images/Fillet1DPanel_Wire.png
new file mode 100644 (file)
index 0000000..668627e
Binary files /dev/null and b/src/FeaturesPlugin/doc/images/Fillet1DPanel_Wire.png differ
diff --git a/src/FeaturesPlugin/doc/images/Fillet1DResult_Vertices.png b/src/FeaturesPlugin/doc/images/Fillet1DResult_Vertices.png
new file mode 100644 (file)
index 0000000..4ec0fa9
Binary files /dev/null and b/src/FeaturesPlugin/doc/images/Fillet1DResult_Vertices.png differ
diff --git a/src/FeaturesPlugin/doc/images/Fillet1DResult_Wire.png b/src/FeaturesPlugin/doc/images/Fillet1DResult_Wire.png
new file mode 100644 (file)
index 0000000..a465525
Binary files /dev/null and b/src/FeaturesPlugin/doc/images/Fillet1DResult_Wire.png differ
diff --git a/src/FeaturesPlugin/doc/images/fillet1d.png b/src/FeaturesPlugin/doc/images/fillet1d.png
new file mode 100644 (file)
index 0000000..a94aba3
Binary files /dev/null and b/src/FeaturesPlugin/doc/images/fillet1d.png differ
diff --git a/src/FeaturesPlugin/doc/images/fillet1d_points.png b/src/FeaturesPlugin/doc/images/fillet1d_points.png
new file mode 100644 (file)
index 0000000..c0f751a
Binary files /dev/null and b/src/FeaturesPlugin/doc/images/fillet1d_points.png differ
diff --git a/src/FeaturesPlugin/doc/images/fillet1d_wire.png b/src/FeaturesPlugin/doc/images/fillet1d_wire.png
new file mode 100644 (file)
index 0000000..62eed22
Binary files /dev/null and b/src/FeaturesPlugin/doc/images/fillet1d_wire.png differ
diff --git a/src/FeaturesPlugin/fillet1d_widget.xml b/src/FeaturesPlugin/fillet1d_widget.xml
new file mode 100644 (file)
index 0000000..e7f3caf
--- /dev/null
@@ -0,0 +1,40 @@
+<source>
+  <toolbox id="creation_method">
+    <box id="by_wires"
+         title="Wires"
+         tooltip="Fillet each sharp corner of the wire"
+         icon="icons/Features/fillet1d_wire.png">
+      <multi_selector id="main_wires"
+                      label="Wires"
+                      icon=""
+                      tooltip="Select wires"
+                      shape_types="wires"
+                      use_choice="false"
+                      concealment="true">
+        <validator id="PartSet_DifferentObjects"/>
+      </multi_selector>
+    </box>
+    <box id="by_vertices"
+         title="Vertices"
+         tooltip="Fillet the specified corners of the wire"
+         icon="icons/Features/fillet1d_points.png">
+      <multi_selector id="main_vertices"
+                      label="Vertices"
+                      icon=""
+                      tooltip="Select vertices"
+                      shape_types="vertices"
+                      use_choice="false"
+                      concealment="true">
+        <validator id="PartSet_DifferentObjects"/>
+        <validator id="FeaturesPlugin_ValidatorFillet1DSelection"/>
+      </multi_selector>
+    </box>
+  </toolbox>
+  <doublevalue id="radius"
+               label="Radius"
+               tooltip="Fillet radius"
+               min="0"
+               default="0">
+    <validator id="GeomValidators_Positive"/>
+  </doublevalue>
+</source>
diff --git a/src/FeaturesPlugin/icons/fillet1d.png b/src/FeaturesPlugin/icons/fillet1d.png
new file mode 100644 (file)
index 0000000..a94aba3
Binary files /dev/null and b/src/FeaturesPlugin/icons/fillet1d.png differ
diff --git a/src/FeaturesPlugin/icons/fillet1d_points.png b/src/FeaturesPlugin/icons/fillet1d_points.png
new file mode 100644 (file)
index 0000000..c0f751a
Binary files /dev/null and b/src/FeaturesPlugin/icons/fillet1d_points.png differ
diff --git a/src/FeaturesPlugin/icons/fillet1d_wire.png b/src/FeaturesPlugin/icons/fillet1d_wire.png
new file mode 100644 (file)
index 0000000..62eed22
Binary files /dev/null and b/src/FeaturesPlugin/icons/fillet1d_wire.png differ
index 02c36a269861c09f918e57ff42e7885306d46a7a..ba80beedda3367cc8fef29f58ce5dc4bcafa7734 100644 (file)
       </feature>
     </group>
     <group id="Features">
+      <feature id="Fillet1D"
+               title="1D-fillet"
+               tooltip="Perform fillet on vertices of a wire"
+               icon="icons/Features/fillet1d.png"
+               auto_preview="true"
+               apply_continue="true"
+               helpfile="fillet1dFeature.html">
+        <source path="fillet1d_widget.xml"/>
+      </feature>
       <feature id="Fillet" title="Fillet" tooltip="Perform fillet on face or edge"
                icon="icons/Features/fillet.png" auto_preview="true" helpfile="filletFeature.html">
         <source path="fillet_widget.xml"/>
index 9d462d7082a52333389ecf381d0548cdd1f9c431..e7d65648a5739c5b709ac583482ff436527fee35 100644 (file)
@@ -23,6 +23,7 @@
 #include <ModelAPI_ResultBody.h>
 #include <ModelAPI_Tools.h>
 
+#include <GeomAPI_Edge.h>
 #include <GeomAPI_Shape.h>
 #include <GeomAPI_ShapeExplorer.h>
 #include <GeomAPI_Wire.h>
index cb2aef956615926a3dc704c2ef10e3eb16ce8910..b785aa77b0e269e31964be9838d4c2493d7b84c3 100644 (file)
 
 #include <Geom_BSplineCurve.hxx>
 
+#include <GeomConvert.hxx>
+
 #define MY_BSPLINE (*(implPtr<Handle_Geom_BSplineCurve>()))
 
-GeomAPI_BSpline::GeomAPI_BSpline(const GeomCurvePtr& theCurve)
+GeomAPI_BSpline::GeomAPI_BSpline (const GeomCurvePtr& theCurve)
 {
   GeomCurvePtr anUntrimmedCurve = theCurve->basisCurve();
   Handle(Geom_Curve) aCurve = anUntrimmedCurve->impl<Handle(Geom_Curve)>();
@@ -76,3 +78,20 @@ bool GeomAPI_BSpline::isPeriodic() const
 {
   return MY_BSPLINE->IsPeriodic();
 }
+
+GeomBSplinePtr GeomAPI_BSpline::convertToBSpline (const GeomCurvePtr& theCurve)
+{
+  GeomCurvePtr anUntrimmedCurve = theCurve->basisCurve();
+  Handle(Geom_Curve) aCurve = anUntrimmedCurve->impl<Handle(Geom_Curve)>();
+  Handle(Geom_BSplineCurve) aBSpl = Handle(Geom_BSplineCurve)::DownCast(aCurve);
+  if (aBSpl.IsNull()) {
+    // convert to b-spline
+    aBSpl = GeomConvert::CurveToBSplineCurve(aCurve);
+  }
+  if (aBSpl.IsNull())
+    throw Standard_ConstructionError("GeomAPI_BSpline: Conversion to B-spline failed");
+  GeomCurvePtr aResCurve (new GeomAPI_Curve());
+  aResCurve->setImpl(new Handle_Geom_BSplineCurve(aBSpl));
+  GeomBSplinePtr aResult (new GeomAPI_BSpline(aResCurve));
+  return aResult;
+}
index 95468bec452e447402013653e038af3e95fb636d..251122dd8c93f270eef921dcc7cef466fd2981fc 100644 (file)
 #include <memory>
 
 class GeomAPI_Pnt;
+class GeomAPI_BSpline;
+
+//! Pointer on the object
+typedef std::shared_ptr<GeomAPI_BSpline> GeomBSplinePtr;
 
 /**\class GeomAPI_BSpline
  * \ingroup DataModel
@@ -36,7 +40,7 @@ class GeomAPI_BSpline : public GeomAPI_Interface
 {
 public:
   /// Creation of B-spline defined by a curve
-  GEOMAPI_EXPORT GeomAPI_BSpline(const GeomCurvePtr& theCurve);
+  GEOMAPI_EXPORT GeomAPI_BSpline (const GeomCurvePtr& theCurve);
 
   /// Degree of B-spline curve
   GEOMAPI_EXPORT int degree() const;
@@ -55,9 +59,9 @@ public:
 
   /// Return \c true if the curve is periodic
   GEOMAPI_EXPORT bool isPeriodic() const;
-};
 
-//! Pointer on the object
-typedef std::shared_ptr<GeomAPI_BSpline> GeomBSplinePtr;
+  /// Convert any curve into a B-spline curve
+  GEOMAPI_EXPORT static GeomBSplinePtr convertToBSpline (const GeomCurvePtr& theCurve);
+};
 
 #endif
index 6233a08c974b1dd86121295f110f9e9e09c652f2..817c683459e1c7fa4655c6e5f1eaae8d6636d0ab 100644 (file)
@@ -175,9 +175,9 @@ bool GeomAPI_Edge::isEllipse() const
   Handle(Geom_Curve) aCurve = BRep_Tool::Curve((const TopoDS_Edge&)aShape, aFirst, aLast);
   if (aCurve.IsNull()) // degenerative edge
     return false;
-  if (aCurve->IsKind(STANDARD_TYPE(Geom_Ellipse)))
-    return true;
-  return false;
+  while (aCurve->IsKind(STANDARD_TYPE(Geom_TrimmedCurve)))
+    aCurve = Handle(Geom_TrimmedCurve)::DownCast(aCurve)->BasisCurve();
+  return aCurve->IsKind(STANDARD_TYPE(Geom_Ellipse));
 }
 
 bool GeomAPI_Edge::isBSpline() const
@@ -234,6 +234,8 @@ std::shared_ptr<GeomAPI_Ellipse> GeomAPI_Edge::ellipse() const
   double aFirst, aLast;
   Handle(Geom_Curve) aCurve = BRep_Tool::Curve((const TopoDS_Edge&)aShape, aFirst, aLast);
   if (!aCurve.IsNull()) {
+    while (aCurve->IsKind(STANDARD_TYPE(Geom_TrimmedCurve)))
+      aCurve = Handle(Geom_TrimmedCurve)::DownCast(aCurve)->BasisCurve();
     Handle(Geom_Ellipse) aElips = Handle(Geom_Ellipse)::DownCast(aCurve);
     if (!aElips.IsNull()) {
       gp_Elips aGpElips = aElips->Elips();
@@ -461,6 +463,22 @@ void GeomAPI_Edge::setLastPointTolerance(const double theTolerance)
   BRep_Builder().UpdateVertex(aVLast, theTolerance);
 }
 
+double GeomAPI_Edge::firstPointTolerance() const
+{
+  TopoDS_Edge anEdge = impl<TopoDS_Edge>();
+  TopoDS_Vertex aVFirst, aVLast;
+  TopExp::Vertices(anEdge, aVFirst, aVLast);
+  return BRep_Tool::Tolerance(aVFirst);
+}
+
+double GeomAPI_Edge::lastPointTolerance() const
+{
+  TopoDS_Edge anEdge = impl<TopoDS_Edge>();
+  TopoDS_Vertex aVFirst, aVLast;
+  TopExp::Vertices(anEdge, aVFirst, aVLast);
+  return BRep_Tool::Tolerance(aVLast);
+}
+
 GeomPointPtr GeomAPI_Edge::middlePoint() const
 {
   GeomPointPtr aMiddlePoint;
index 89eecc8e9a95e0d52aec96ea674d698dac2f4bdf..6accbf978b1c7d5c3bf4ca8c4e29affe2d63a817 100644 (file)
@@ -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<GeomAPI_Pnt> middlePoint() const;
index 318819f9996385b4873676f505ace738cb535da9..635c9b6fbbdf79332f27588338444e5276deb35c 100644 (file)
@@ -18,6 +18,9 @@
 //
 
 #include <GeomAPI_WireExplorer.h>
+
+#include <GeomAPI_Edge.h>
+#include <GeomAPI_Vertex.h>
 #include <GeomAPI_Wire.h>
 
 #include <BRepTools_WireExplorer.hxx>
@@ -49,11 +52,19 @@ void GeomAPI_WireExplorer::next()
   MY_EXPLORER->Next();
 }
 
-std::shared_ptr<GeomAPI_Shape> GeomAPI_WireExplorer::current()
+std::shared_ptr<GeomAPI_Edge> GeomAPI_WireExplorer::current()
+{
+  const TopoDS_Edge& aShape = MY_EXPLORER->Current();
+  std::shared_ptr<GeomAPI_Edge> aGeomShape(new GeomAPI_Edge());
+  aGeomShape->setImpl(new TopoDS_Edge(aShape));
+  return aGeomShape;
+}
+
+std::shared_ptr<GeomAPI_Vertex> GeomAPI_WireExplorer::currentVertex()
 {
-  const TopoDS_Shape& aShape = MY_EXPLORER->Current();
-  std::shared_ptr<GeomAPI_Shape> aGeomShape(new GeomAPI_Shape());
-  aGeomShape->setImpl(new TopoDS_Shape(aShape));
+  const TopoDS_Vertex& aShape = MY_EXPLORER->CurrentVertex();
+  std::shared_ptr<GeomAPI_Vertex> aGeomShape(new GeomAPI_Vertex());
+  aGeomShape->setImpl(new TopoDS_Vertex(aShape));
   return aGeomShape;
 }
 
index 292128e902efaab229d184501f54a4953ea878d2..97e6eb180361b24b17566997f185a2b3cf13afff 100644 (file)
@@ -23,7 +23,9 @@
 #include <GeomAPI.h>
 #include <GeomAPI_Interface.h>
 
+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<GeomAPI_Shape> current();
+  GEOMAPI_EXPORT std::shared_ptr<GeomAPI_Edge> 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<GeomAPI_Vertex> currentVertex();
 
   /// Clears the content of the explorer. It will return False on more().
   GEOMAPI_EXPORT void clear();
index 78fba30bc9e9780e2ca854377e9555c9756d2446..95e749c10d2d5263ee845b513f73b2659f70365e 100644 (file)
@@ -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
index 04a925704af825305adc8c64cf1f9b27751cdb0f..774f35fd9daf0a580ba54e66e076959a550729c4 100644 (file)
@@ -58,6 +58,7 @@
 %shared_ptr(GeomAlgoAPI_Copy)
 %shared_ptr(GeomAlgoAPI_Symmetry)
 %shared_ptr(GeomAlgoAPI_MapShapesAndAncestors)
+%shared_ptr(GeomAlgoAPI_WireBuilder)
 
 // all supported interfaces
 %include "GeomAlgoAPI_MakeShape.h"
index ad1850a08f8966cdeea5a27a084a31003bb3938a..ab6499bf82bdbc10c5dbddaeaf8c7fa31fd87a7f 100644 (file)
 #include <GeomAPI_Vertex.h>
 #include <GeomAPI_ShapeExplorer.h>
 
+#include <Approx_ParametrizationType.hxx>
 #include <BRepBuilderAPI_MakeEdge.hxx>
-#include <TopoDS.hxx>
-#include <TopoDS_Edge.hxx>
-#include <TopExp_Explorer.hxx>
-#include <TColgp_HArray1OfPnt.hxx>
+#include <BSplSLib.hxx>
+#include <Geom_BSplineCurve.hxx>
+#include <Geom_BSplineSurface.hxx>
 #include <GeomAPI_Interpolate.hxx>
+#include <GeomAPI_PointsToBSplineSurface.hxx>
 #include <gp_Pnt.hxx>
 #include <Precision.hxx>
+#include <TColgp_Array2OfPnt.hxx>
+#include <TColgp_HArray1OfPnt.hxx>
+#include <TopExp_Explorer.hxx>
+#include <TopoDS.hxx>
+#include <TopoDS_Edge.hxx>
 
 
-static void reorder(Handle(TColgp_HArray1OfPnt)& thePoints);
-
-//=================================================================================================
 GeomEdgePtr GeomAlgoAPI_CurveBuilder::edge(const std::list<GeomPointPtr>& thePoints,
-  const bool theIsClosed,
-  const bool theIsToReorder,
-  const GeomDirPtr& theStartTangent,
-  const GeomDirPtr& theEndTangent)
+                                           const bool thePeriodic,
+                                           const bool theIsToReorder,
+                                           const GeomDirPtr& theStartTangent,
+                                           const GeomDirPtr& theEndTangent)
 {
-  // Prepare points array
-  Handle(TColgp_HArray1OfPnt) aPoints = new TColgp_HArray1OfPnt(1, (int)thePoints.size());
-  std::list<GeomPointPtr>::const_iterator anIt = thePoints.begin();
-  for (int i = 1; anIt != thePoints.end(); anIt++, i++) {
-    GeomPointPtr aPoint = *anIt;
-    aPoints->SetValue(i, aPoint->impl<gp_Pnt>());
-  }
+  std::list<GeomPointPtr> aPointsCopy = thePoints;
 
   // If the curve to be closed - remove last point if it is too close to the first one
-  bool isClose = aPoints->First().Distance(aPoints->Last()) <= gp::Resolution();
-  if (isClose && theIsClosed) {
-    aPoints->Resize(aPoints->Lower(), aPoints->Upper() - 1, Standard_True);
+  bool isClose = aPointsCopy.front()->distance(aPointsCopy.back()) <= gp::Resolution();
+  if (isClose && thePeriodic) {
+    aPointsCopy.pop_back();
   }
 
   // Reorder points if required
   if (theIsToReorder) {
-    reorder(aPoints);
+    reorderPoints(aPointsCopy);
+  }
+
+  // Prepare points array
+  Handle(TColgp_HArray1OfPnt) aPoints = new TColgp_HArray1OfPnt(1, (int)aPointsCopy.size());
+  std::list<GeomPointPtr>::const_iterator anIt = aPointsCopy.begin();
+  for (int i = 1; anIt != aPointsCopy.end(); anIt++, i++) {
+    GeomPointPtr aPoint = *anIt;
+    aPoints->SetValue(i, aPoint->impl<gp_Pnt>());
   }
 
   // Initialize interpolator
-  GeomAPI_Interpolate anInterp(aPoints, theIsClosed, gp::Resolution());
+  GeomAPI_Interpolate anInterp(aPoints, thePeriodic, gp::Resolution());
 
   // Assign tangents if defined
   if (theStartTangent && theEndTangent) {
@@ -92,54 +97,116 @@ GeomEdgePtr GeomAlgoAPI_CurveBuilder::edge(const std::list<GeomPointPtr>& thePoi
   return aResultShape;
 }
 
-//================   Auxiliary functions   ========================================================
-void reorder(Handle(TColgp_HArray1OfPnt)& thePoints)
+GeomEdgePtr GeomAlgoAPI_CurveBuilder::approximate(const std::list<GeomPointPtr>& thePoints,
+                                                  const bool thePeriodic,
+                                                  const double thePrecision)
+{
+  // Prepare points array to be able to build a surface.
+  // This surface is based on two sets of points: the first is an original and
+  // the second is shifted along orthogonal direction.
+  // This is a workaround, because GeomAPI_PointsToBSpline algorithm cannot produce
+  // the periodic curve, but GeomAPI_PointsToBSplineSurface can.
+  TColgp_Array2OfPnt aPoints(1, (int)thePoints.size(), 1, 2);
+  gp_Pnt aPlaneBase[3]; // base points to calculate the normal direction
+  int aNbPlanePoints = 0;
+  gp_Dir aNormal;
+  std::list<GeomPointPtr>::const_iterator anIt = thePoints.begin();
+  for (int i = 1; anIt != thePoints.end(); anIt++, i++) {
+    const gp_Pnt& aPoint = (*anIt)->impl<gp_Pnt>();
+    aPoints.SetValue(i, 1, aPoint);
+    aPoints.SetValue(i, 2, aPoint);
+    if (aNbPlanePoints < 3) {
+      if (aNbPlanePoints == 0 ||
+          aPoint.SquareDistance(aPlaneBase[0]) > Precision::SquareConfusion())
+        aPlaneBase[aNbPlanePoints++] = aPoint;
+      if (aNbPlanePoints == 3) {
+        gp_Vec aVec12(aPlaneBase[0], aPlaneBase[1]);
+        gp_Vec aVec13(aPlaneBase[0], aPlaneBase[2]);
+        if (aVec12.CrossSquareMagnitude(aVec13) > Precision::SquareConfusion())
+          aNormal = gp_Dir(aVec12 ^ aVec13);
+        else
+          --aNbPlanePoints;
+      }
+    }
+  }
+  if (aNbPlanePoints < 3)
+    aNormal = gp::DZ();
+  // shifted points
+  for (int i = aPoints.LowerRow(); i <= aPoints.UpperRow(); i++)
+    aPoints.ChangeValue(i, 2).ChangeCoord() += aNormal.XYZ();
+
+  // If the curve to be closed - remove last point if it is too close to the first one
+  bool isClose = aPoints.Value(aPoints.LowerRow(), 1).Distance(
+                 aPoints.Value(aPoints.UpperRow(), 1)) <= gp::Resolution();
+  if (isClose && thePeriodic) {
+    aPoints.Resize(aPoints.LowerRow(), aPoints.UpperRow() - 1,
+                   aPoints.LowerCol(), aPoints.UpperCol(), Standard_True);
+  }
+
+  // Initialize and perform approximator
+  static const Standard_Integer DEGREE_MIN = 3;
+  static const Standard_Integer DEGREE_MAX = 8;
+  GeomAPI_PointsToBSplineSurface anApprox;
+  anApprox.Init(aPoints, Approx_ChordLength, DEGREE_MIN, DEGREE_MAX,
+                GeomAbs_C2, thePrecision, thePeriodic);
+
+  // Set result in form of edge
+  TopoDS_Edge anEdge;
+  if (anApprox.IsDone()) {
+    // build a curve along U-direction of the surface
+    Handle(Geom_BSplineSurface) aSurface = anApprox.Surface();
+    Handle(Geom_Curve) aCurve = aSurface->VIso(aSurface->VKnots().First());
+
+    anEdge = BRepBuilderAPI_MakeEdge(aCurve).Edge();
+  }
+
+  GeomEdgePtr aResultShape(new GeomAPI_Edge);
+  aResultShape->setImpl(new TopoDS_Shape(anEdge));
+
+  return aResultShape;
+}
+
+void GeomAlgoAPI_CurveBuilder::reorderPoints(std::list<GeomPointPtr>& thePoints)
 {
-  if (thePoints->Length() < 3) {
+  if (thePoints.size() < 3) {
     return;
   }
 
-  int aNbPoints = thePoints->Length();
   int aNbDup = 0;
-  gp_Pnt aPrevPnt = thePoints->Value(1);
-  for (int i = 1; i < aNbPoints; i++) {
-    gp_Pnt aPnt = thePoints->Value(i);
-    int aNearest = 0;
+  std::list<GeomPointPtr>::iterator aPIt = thePoints.begin();
+  GeomPointPtr aPrevPnt = *aPIt;
+  for (; aPIt != thePoints.end(); ++aPIt) {
+    GeomPointPtr aPnt = *aPIt;
+    std::list<GeomPointPtr>::iterator aNextIt = aPIt;
+    std::list<GeomPointPtr>::iterator aNearestIt = ++aNextIt;
     double aMinDist = RealLast();
-    for (int j = i + 1; j <= aNbPoints; j++) {
-      double aDist = aPnt.SquareDistance(thePoints->Value(j));
+    while (aNextIt != thePoints.end()) {
+      double aDist = aPnt->distance(*aNextIt);
+      if (aDist < Precision::Confusion()) {
+        // remove duplicates
+        std::list<GeomPointPtr>::iterator aRemoveIt = aNextIt++;
+        thePoints.erase(aRemoveIt);
+        // update iterator showing the nearest point, because it may become invalid
+        aNearestIt = aPIt; ++aNearestIt;
+        continue;
+      }
       if (aDist < aMinDist && (aMinDist - aDist) > Precision::Confusion()) {
-        aNearest = j;
+        aNearestIt = aNextIt;
         aMinDist = aDist;
       }
+      ++aNextIt;
     }
-    if (aNearest > 0 && aNearest != i + 1) {
+    aNextIt = aPIt; ++aNextIt;
+    if (aNearestIt != aNextIt) {
       // Keep given order of points to use it in case of equidistant candidates
       //              .--<---<--.
       //             |           |
       // o  o  o  c  o->o->o->o->n  o  o
       //          |  |           |
       //          i i+1       nearest
-      gp_Pnt aNearestPnt = thePoints->Value(aNearest);
-      for (int j = aNearest; j > i + 1; j--) {
-        thePoints->SetValue(j, thePoints->Value(j - 1));
-      }
-      thePoints->SetValue(i + 1, aNearestPnt);
-    }
-    if (aPrevPnt.Distance(thePoints->Value(i + 1)) <= Precision::Confusion())
-      aNbDup++;
-    else
-      aPrevPnt = thePoints->Value(i + 1);
-  }
-
-  if (aNbDup > 0) {
-    Handle(TColgp_HArray1OfPnt) aTmpPoints = new TColgp_HArray1OfPnt(1, aNbPoints - aNbDup);
-    for (int j = 1, i = 1; i <= aNbPoints; i++) {
-      if (i == 1 || aPrevPnt.Distance(thePoints->Value(i)) > Precision::Confusion()) {
-        aTmpPoints->SetValue(j++, thePoints->Value(i));
-        aPrevPnt = thePoints->Value(i);
-      }
+      GeomPointPtr aPointToMove = *aNearestIt;
+      thePoints.erase(aNearestIt);
+      thePoints.insert(aNextIt, aPointToMove);
     }
-    thePoints = aTmpPoints;
   }
 }
\ No newline at end of file
index 2419422d72342764e98a3c3b77f4052696c35616..8f5b651749d0b1df6af446d809e7f14751eb4534 100644 (file)
 
 /// \class GeomAlgoAPI_CurveBuilder
 /// \ingroup DataAlgo
-/// \brief Allows to create interpolation curve.
+/// \brief Allows to create a curve by the list of point.
 class GeomAlgoAPI_CurveBuilder
 {
  public:
    /// \brief Creates an interpolation curve from points.
    /// \param[in] thePoints list of points.
-   /// \param[in] theIsClosed defines whether the curve to be closed.
+   /// \param[in] thePeriodic defines whether the curve to be periodic.
    /// \param[in] theIsToReorder defines whether to change the order of points to construct
    ///            the shortest curve.
    /// \param[in] theStartTangent vector tangent to the start of curve.
    /// \param[in] theEndTangent vector tangent to the end of curve.
    /// \return Interpolation curve (edge). Empty in case of error or bad input.
    GEOMALGOAPI_EXPORT static GeomEdgePtr edge(const std::list<GeomPointPtr>& thePoints,
-                                              const bool theIsClosed,
+                                              const bool thePeriodic,
                                               const bool theIsToReorder,
                                               const GeomDirPtr& theStartTangent,
                                               const GeomDirPtr& theEndTangent);
+
+   /// \brief Approximate the given points by a curve.
+   /// \param[in] thePoints list of points.
+   /// \param[in] thePeriodic defines whether the curve to be periodic.
+   /// \param[in] thePrecision how close the curve should be to the points.
+   /// \return Apporimation curve (edge). Empty in case of error or bad input.
+   GEOMALGOAPI_EXPORT static GeomEdgePtr approximate(const std::list<GeomPointPtr>& thePoints,
+                                                     const bool thePeriodic,
+                                                     const double thePrecision);
+
+   /// \brief Reoder the list of points to get a polyline of minimal length
+   GEOMALGOAPI_EXPORT static void reorderPoints(std::list<GeomPointPtr>& thePoints);
 };
 
 #endif
diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp
new file mode 100644 (file)
index 0000000..4408220
--- /dev/null
@@ -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 <GeomAlgoAPI_Fillet1D.h>
+
+#include <GeomAlgoAPI_Copy.h>
+#include <GeomAlgoAPI_MapShapesAndAncestors.h>
+#include <GeomAlgoAPI_ShapeTools.h>
+
+#include <GeomAPI_Edge.h>
+#include <GeomAPI_Pln.h>
+#include <GeomAPI_Pnt.h>
+#include <GeomAPI_Wire.h>
+#include <GeomAPI_WireExplorer.h>
+
+#include <GEOMImpl_Fillet1d.hxx>
+
+#include <BRep_Builder.hxx>
+#include <BRepTools_WireExplorer.hxx>
+#include <ShapeFix_Wire.hxx>
+#include <TopoDS.hxx>
+#include <TopoDS_Wire.hxx>
+
+static GeomShapePtr convert(const TopoDS_Shape& theShape)
+{
+  GeomShapePtr aNewShape(new GeomAPI_Shape);
+  aNewShape->setImpl<TopoDS_Shape>(new TopoDS_Shape(theShape));
+  return aNewShape;
+}
+
+static void substituteNewEdge(GeomEdgePtr theEdge,
+    std::map<GeomShapePtr, ListOfShape, GeomAPI_Shape::Comparator>& theMap)
+{
+  std::map<GeomShapePtr, ListOfShape, GeomAPI_Shape::Comparator>::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_Shape>());
+    TopoDS_Edge anEdge2 = TopoDS::Edge(anEdges.back()->impl<TopoDS_Shape>());
+
+    // create fillet builder
+    GEOMImpl_Fillet1d aFilletBuilder(anEdge1, anEdge2, aPlane->impl<gp_Pln>());
+    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<gp_Pnt>(), 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<TopoDS_Shape>()));
+  }
+  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<TopoDS_Shape>()));
+  }
+  // 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<TopoDS_Shape>());
+  ListOfShape aNewEdges;
+  modified(aBaseFirstEdge, aNewEdges);
+  if (!aNewEdges.empty())
+    aFirstEdge = TopoDS::Edge(aNewEdges.front()->impl<TopoDS_Shape>());
+  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<GeomAPI_Shape> 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 (file)
index 0000000..578f91c
--- /dev/null
@@ -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 <GeomAlgoAPI.h>
+#include <GeomAlgoAPI_MakeShape.h>
+
+#include <GeomAPI_Shape.h>
+
+/// \class GeomAlgoAPI_Fillet1D
+/// \ingroup DataAlgo
+/// \brief Perform fillet on vertices of a wire
+class GeomAlgoAPI_Fillet1D : public GeomAlgoAPI_MakeShape
+{
+  typedef std::map<GeomShapePtr, ListOfShape, GeomAPI_Shape::Comparator> 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
index 212bef8bdf65914a42f4fa8ab4c7e5a26a2b150e..dccf9bd131f4df22ed8ac3542869d74fea253cf8 100644 (file)
 
 #include "GeomAlgoAPI_Offset.h"
 
+#include <GeomAPI_Pln.h>
+
 #include <BRepOffsetAPI_MakeOffsetShape.hxx>
+#include <BRepOffsetAPI_MakeOffset.hxx>
+
+#include <BRepBuilderAPI_MakeWire.hxx>
+#include <BRepBuilderAPI_MakeFace.hxx>
 
+#include <TopoDS.hxx>
+#include <TopoDS_Wire.hxx>
 
 GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomShapePtr& theShape,
                                        const double theOffsetValue)
@@ -53,3 +61,45 @@ void GeomAlgoAPI_Offset::generated(const GeomShapePtr theOldShape,
     // nothing is generated
   }
 }
+
+GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomPlanePtr& thePlane,
+                                       const GeomShapePtr& theEdgeOrWire,
+                                       const double theOffsetValue)
+{
+  // 1. Make wire from edge, if need
+  TopoDS_Wire aWire;
+  TopoDS_Shape anEdgeOrWire = theEdgeOrWire->impl<TopoDS_Shape>();
+  if (anEdgeOrWire.ShapeType() == TopAbs_WIRE) {
+    aWire = TopoDS::Wire(anEdgeOrWire);
+  } else {
+    if (anEdgeOrWire.ShapeType() == TopAbs_EDGE) {
+      BRepBuilderAPI_MakeWire aWireBuilder;
+      aWireBuilder.Add(TopoDS::Edge(anEdgeOrWire));
+      if (aWireBuilder.IsDone()) {
+        aWire = aWireBuilder.Wire();
+      }
+    }
+  }
+  if (aWire.IsNull())
+    return;
+
+  // 2. Make invalid face to pass it in Offset algorithm
+  BRepBuilderAPI_MakeFace aFaceBuilder (thePlane->impl<gp_Pln>(), aWire);
+  const TopoDS_Face& aFace = aFaceBuilder.Face();
+
+  // 3. Make Offset
+  BRepOffsetAPI_MakeOffset* aParal = new BRepOffsetAPI_MakeOffset;
+  setImpl(aParal);
+  setBuilderType(OCCT_BRepBuilderAPI_MakeShape);
+
+  Standard_Boolean isOpenResult = !aWire.Closed();
+  aParal->Init(aFace, GeomAbs_Arc, isOpenResult);
+  aParal->Perform(theOffsetValue, 0.);
+  if (aParal->IsDone()) {
+    TopoDS_Shape anOffset = aParal->Shape();
+    GeomShapePtr aResult(new GeomAPI_Shape());
+    aResult->setImpl(new TopoDS_Shape(anOffset));
+    setShape(aResult);
+    setDone(true);
+  }
+}
index 56663fb5c353c4ff2317e8b89fecc8259e25d877..46623465e6ec8bec8ec68b2e5ee016321aa68628 100644 (file)
@@ -23,6 +23,8 @@
 #include <GeomAlgoAPI.h>
 #include <GeomAlgoAPI_MakeShape.h>
 
+class GeomAPI_Pln;
+
 /// \class GeomAlgoAPI_Offset
 /// \ingroup DataAlgo
 /// \brief Perform 3D offset for the shape
@@ -33,12 +35,21 @@ public:
   GEOMALGOAPI_EXPORT GeomAlgoAPI_Offset(const GeomShapePtr& theShape,
                                         const double theOffsetValue);
 
+  /// \brief Perform the offset algorithm on the plane
+  /// \param[in] thePlane base plane for all offsets
+  /// \param[in] theEdgesOrWire base shapes
+  /// \param[in] theOffsetValue offset distance, it can be negative
+  GEOMALGOAPI_EXPORT GeomAlgoAPI_Offset(const std::shared_ptr<GeomAPI_Pln>& thePlane,
+                                        const GeomShapePtr& theEdgeOrWire,
+                                        const double theOffsetValue);
+
   /// \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);
 
+
 private:
   /// \brief Perform offset operation
   void build(const GeomShapePtr& theShape, const double theOffsetValue);
index 94c22465a7f3263a2e0a4abf26dec9a905b90deb..f0c836ac26d98cf1a5d6441d38272a9ed964674e 100644 (file)
@@ -42,7 +42,8 @@ GeomCurvePtr GeomAlgoAPI_Projection::project(const GeomCurvePtr& theCurve)
   Handle(Geom_Curve) aCurve = theCurve->impl<Handle_Geom_Curve>();
   Handle(Geom_Plane) aPlane = new Geom_Plane(myPlane->impl<gp_Ax3>());
 
-  Handle(Geom_Curve) aProj = GeomProjLib::Project(aCurve, aPlane);
+  Handle(Geom_Curve) aProj =
+      GeomProjLib::ProjectOnPlane(aCurve, aPlane, aPlane->Axis().Direction(), false);
 
   GeomCurvePtr aProjCurve(new GeomAPI_Curve);
   aProjCurve->setImpl(new Handle_Geom_Curve(aProj));
index d35f58bade2cd907ef1e4bdb7642665b0b996e3c..5b04d9f066dbc3a0ebde05a8a57c39915c32caab 100644 (file)
@@ -50,6 +50,7 @@
 #include <BRepTopAdaptor_FClass2d.hxx>
 #include <BRepClass_FaceClassifier.hxx>
 #include <BRepLib_CheckCurveOnSurface.hxx>
+#include <BRepLProp.hxx>
 
 #include <BOPAlgo_Builder.hxx>
 
@@ -855,6 +856,44 @@ std::shared_ptr<GeomAPI_Shape>
   return anOuterWire;
 }
 
+//==================================================================================================
+static bool boundaryOfEdge(const std::shared_ptr<GeomAPI_Edge> theEdge,
+                          const std::shared_ptr<GeomAPI_Vertex> 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<GeomAPI_Edge> theEdge1,
+                                       const std::shared_ptr<GeomAPI_Edge> theEdge2,
+                                       const std::shared_ptr<GeomAPI_Vertex> theTgPoint)
+{
+  double aParE1 = 0, aParE2 = 0;
+  if (!boundaryOfEdge(theEdge1, theTgPoint, aParE1) ||
+      !boundaryOfEdge(theEdge2, theTgPoint, aParE2))
+    return false;
+
+  BRepAdaptor_Curve aC1(theEdge1->impl<TopoDS_Edge>());
+  BRepAdaptor_Curve aC2(theEdge2->impl<TopoDS_Edge>());
+  return BRepLProp::Continuity(aC1, aC2, aParE1, aParE2) >= GeomAbs_G1;
+}
+
 //==================================================================================================
 bool GeomAlgoAPI_ShapeTools::isParallel(const std::shared_ptr<GeomAPI_Edge> theEdge,
                                         const std::shared_ptr<GeomAPI_Face> theFace)
index e321dd3e365c82e2fd54f4cdb5f1324071c185a8..4e1e6523a0d95ce80cc6223af87da34f8c672c2e 100644 (file)
@@ -141,6 +141,11 @@ public:
   GEOMALGOAPI_EXPORT static
     std::shared_ptr<GeomAPI_Shape> getFaceOuterWire(const std::shared_ptr<GeomAPI_Shape> theFace);
 
+  /// \return \c true if edges are tangent in the specified point
+  GEOMALGOAPI_EXPORT static bool isTangent(const std::shared_ptr<GeomAPI_Edge> theEdge1,
+                                           const std::shared_ptr<GeomAPI_Edge> theEdge2,
+                                           const std::shared_ptr<GeomAPI_Vertex> theTgPoint);
+
   /// \return true if edge is parallel to face.
   GEOMALGOAPI_EXPORT static bool isParallel(const std::shared_ptr<GeomAPI_Edge> theEdge,
                                             const std::shared_ptr<GeomAPI_Face> theFace);
index bfc4cb1493c8f067e55728f833391a5d7dffc084..2a6c42497fcdc364666f3b24fec93a51477667ea 100644 (file)
 #include <GeomAPI_Vertex.h>
 #include <GeomAPI_ShapeExplorer.h>
 
+#include <BRep_Builder.hxx>
 #include <BRep_Tool.hxx>
 #include <BRepBuilderAPI_MakeWire.hxx>
+#include <BRepTools_ReShape.hxx>
 #include <Geom_Curve.hxx>
 #include <Precision.hxx>
 #include <TopoDS.hxx>
 #include <TopoDS_Wire.hxx>
+#include <TopExp.hxx>
 #include <TopExp_Explorer.hxx>
 
 #include <cmath>
@@ -91,14 +94,21 @@ public:
   }
 };
 
-//=================================================================================================
-GeomShapePtr GeomAlgoAPI_WireBuilder::wire(const ListOfShape& theShapes)
+static GeomShapePtr fromTopoDS(const TopoDS_Shape& theShape)
+{
+  GeomShapePtr aResultShape(new GeomAPI_Shape());
+  aResultShape->setImpl(new TopoDS_Shape(theShape));
+  return aResultShape;
+}
+
+GeomAlgoAPI_WireBuilder::GeomAlgoAPI_WireBuilder(const ListOfShape& theShapes,
+                                                 const bool theForceOpenWire)
 {
   TopTools_ListOfShape aListOfEdges;
   SetOfEdges aProcessedEdges;
 
   ListOfShape::const_iterator anIt = theShapes.cbegin();
-  for(; anIt != theShapes.cend(); ++anIt) {
+  for (; anIt != theShapes.cend(); ++anIt) {
     TopoDS_Shape aShape = (*anIt)->impl<TopoDS_Shape>();
     switch(aShape.ShapeType()) {
       case TopAbs_EDGE: {
@@ -108,7 +118,7 @@ GeomShapePtr GeomAlgoAPI_WireBuilder::wire(const ListOfShape& theShapes)
         break;
       }
       case TopAbs_WIRE: {
-        for(TopExp_Explorer anExp(aShape, TopAbs_EDGE); anExp.More(); anExp.Next()) {
+        for (TopExp_Explorer anExp(aShape, TopAbs_EDGE); anExp.More(); anExp.Next()) {
           TopoDS_Shape anEdge = anExp.Current();
           anEdge.Orientation(TopAbs_FORWARD);
           // if the edge was already processed, remove it to keep original order of the current wire
@@ -123,21 +133,99 @@ GeomShapePtr GeomAlgoAPI_WireBuilder::wire(const ListOfShape& theShapes)
         }
         break;
       }
-      default: {
-        return GeomShapePtr();
-      }
+    default:
+      break;
     }
   }
 
-  BRepBuilderAPI_MakeWire aWireBuilder;
-  aWireBuilder.Add(aListOfEdges);
-  if(aWireBuilder.Error() != BRepBuilderAPI_WireDone) {
-    return GeomShapePtr();
+  bool isSplitWire = false;
+  gp_Pnt aSplitPoint;
+  if (theForceOpenWire && aListOfEdges.Size() > 1) {
+    // find a vertex to split the wire
+    TopoDS_Vertex V1[2];
+    TopExp::Vertices(TopoDS::Edge(aListOfEdges.First()), V1[0], V1[1]);
+    TopoDS_Vertex V2[2];
+    TopExp::Vertices(TopoDS::Edge(aListOfEdges.Last()), V2[0], V2[1]);
+    gp_Pnt P1[2] = { BRep_Tool::Pnt(V1[0]), BRep_Tool::Pnt(V1[1]) };
+    gp_Pnt P2[2] = { BRep_Tool::Pnt(V2[0]), BRep_Tool::Pnt(V2[1]) };
+    double Tol1[2] = { BRep_Tool::Tolerance(V1[0]), BRep_Tool::Tolerance(V1[1]) };
+    double Tol2[2] = { BRep_Tool::Tolerance(V2[0]), BRep_Tool::Tolerance(V2[1]) };
+    for (int i = 0; i < 2 && !isSplitWire; ++i)
+      for (int j = 0; j < 2 && !isSplitWire; ++j)
+        if (P1[i].Distance(P2[j]) < Max(Tol1[i], Tol2[j])) {
+          aSplitPoint = P1[i];
+          isSplitWire = true;
+        }
   }
 
-  GeomShapePtr aResultShape(new GeomAPI_Shape());
-  aResultShape->setImpl(new TopoDS_Shape(aWireBuilder.Wire()));
-  return aResultShape;
+  BRepBuilderAPI_MakeWire* aWireBuilder = new BRepBuilderAPI_MakeWire;
+  aWireBuilder->Add(aListOfEdges);
+  if (aWireBuilder->Error() == BRepBuilderAPI_WireDone) {
+    setImpl(aWireBuilder);
+    setBuilderType(OCCT_BRepBuilderAPI_MakeShape);
+
+    // split the result wire
+    TopoDS_Wire aWire = aWireBuilder->Wire();
+    if (isSplitWire && BRep_Tool::IsClosed(aWire)) {
+      TopoDS_Wire aNewWire;
+      BRep_Builder aBuilder;
+      aBuilder.MakeWire(aNewWire);
+      for (TopExp_Explorer anExp(aWire, TopAbs_EDGE); anExp.More(); anExp.Next()) {
+        TopoDS_Edge aNewCurrent = TopoDS::Edge(anExp.Current());
+        if (isSplitWire) {
+          bool isToReshape = false;
+          BRepTools_ReShape aReshape;
+          TopoDS_Vertex aVF, aVL;
+          TopExp::Vertices(aNewCurrent, aVF, aVL);
+          gp_Pnt aPF = BRep_Tool::Pnt(aVF);
+          double aTolF = BRep_Tool::Tolerance(aVF);
+          gp_Pnt aPL = BRep_Tool::Pnt(aVL);
+          double aTolL = BRep_Tool::Tolerance(aVL);
+          if (aSplitPoint.SquareDistance(aPF) < aTolF * aTolF) {
+            aReshape.Replace(aVF, aReshape.CopyVertex(aVF));
+            isToReshape = true;
+          }
+          else if (aSplitPoint.SquareDistance(aPL) < aTolL * aTolL) {
+            aReshape.Replace(aVL, aReshape.CopyVertex(aVL));
+            isToReshape = true;
+          }
+          if (isToReshape) {
+            aNewCurrent = TopoDS::Edge(aReshape.Apply(aNewCurrent));
+            isSplitWire = false; // no need to continue splitting
+          }
+        }
+        aBuilder.Add(aNewWire, aNewCurrent);
+      }
+      aWire = aNewWire;
+    }
+
+    // store generated/modified shapes
+    for (TopTools_ListOfShape::Iterator aBaseIt(aListOfEdges); aBaseIt.More(); aBaseIt.Next()) {
+      TopoDS_Edge aBaseCurrent = TopoDS::Edge(aBaseIt.Value());
+      Standard_Real aFirst, aLast;
+      Handle(Geom_Curve) aBaseCurve = BRep_Tool::Curve(aBaseCurrent, aFirst, aLast);
+
+      for (TopExp_Explorer anExp(aWire, TopAbs_EDGE); anExp.More(); anExp.Next()) {
+        TopoDS_Edge aNewCurrent = TopoDS::Edge(anExp.Current());
+        Handle(Geom_Curve) aNewCurve = BRep_Tool::Curve(aNewCurrent, aFirst, aLast);
+        if (aBaseCurve == aNewCurve) {
+          GeomShapePtr aBaseShape = fromTopoDS(aBaseCurrent);
+          GeomShapePtr aNewShape = fromTopoDS(aNewCurrent);
+          addGenerated(aBaseShape, aNewShape);
+          addModified(aBaseShape, aNewShape);
+        }
+      }
+    }
+
+    setShape(fromTopoDS(aWire));
+    setDone(true);
+  }
+}
+
+//=================================================================================================
+GeomShapePtr GeomAlgoAPI_WireBuilder::wire(const ListOfShape& theShapes)
+{
+  return GeomAlgoAPI_WireBuilder(theShapes).shape();
 }
 
 //=================================================================================================
index 0ae048a2acb51e3d0fac7c30d55bd090c196ae5b..36b2d10fcbb961bb8a12f8bf7862823f658b654e 100644 (file)
 #define GeomAlgoAPI_WireBuilder_H_
 
 #include "GeomAlgoAPI.h"
+#include "GeomAlgoAPI_MakeShapeCustom.h"
 
 #include <GeomAPI_Shape.h>
 
 /// \class GeomAlgoAPI_WireBuilder
 /// \ingroup DataAlgo
 /// \brief Allows to create wire-shapes by different parameters.
-class GeomAlgoAPI_WireBuilder
+class GeomAlgoAPI_WireBuilder : public GeomAlgoAPI_MakeShapeCustom
 {
  public:
+   /// \brief Creates a wire from edges and wires.
+   /// \param[in] theShapes list of shapes. Only edges and wires allowed.
+   /// \param[in] theForceOpenWire indicates the necessity to split wire
+   ///                             in the first vertex if it becomes closed.
+   /// The edges are not to be consecutive.
+   /// But they are to be all connected geometrically or topologically.
+   GEOMALGOAPI_EXPORT GeomAlgoAPI_WireBuilder(const ListOfShape& theShapes,
+                                              const bool theForceOpenWire = false);
+
    /// \brief Creates a wire from edges and wires.
    /// \param[in] theShapes list of shapes. Only edges and wires allowed.
    /// The edges are not to be consecutive.
index a855e00cadc21fec357fa22a806d55e3c9abe113..83c67995933d1a415c08a6c4a3ef2d865b074a99 100644 (file)
@@ -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 (file)
index 0000000..b9aef21
--- /dev/null
@@ -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 <BRep_Tool.hxx>
+#include <BRepAdaptor_Curve.hxx>
+#include <BRepBuilderAPI_MakeEdge.hxx>
+#include <ElCLib.hxx>
+#include <ElSLib.hxx>
+#include <gp_Circ.hxx>
+#include <Geom2d_Line.hxx>
+#include <Geom2dAPI_ProjectPointOnCurve.hxx>
+#include <Geom2dAPI_InterCurveCurve.hxx>
+#include <GeomAPI_ProjectPointOnCurve.hxx>
+#include <GeomProjLib.hxx>
+#include <Geom_Circle.hxx>
+#include <Precision.hxx>
+#include <TColStd_ListIteratorOfListOfReal.hxx>
+#include <IntRes2d_IntersectionSegment.hxx>
+#include <TopExp.hxx>
+
+#include <Standard_NotImplemented.hxx>
+
+
+/**
+ * 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 (file)
index 0000000..f49294f
--- /dev/null
@@ -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 <TopoDS_Edge.hxx>
+
+#include <Geom_Plane.hxx>
+#include <Geom2d_Curve.hxx>
+
+#include <gp_Pnt.hxx>
+
+#include <TColStd_ListOfReal.hxx>
+#include <TColStd_SequenceOfReal.hxx>
+#include <TColStd_SequenceOfInteger.hxx>
+
+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
index 9db430b22cb73ea09c31581b303c387b2e81eef2..d6602bba7ade5d9c5900eaa945753209c49a21c3 100644 (file)
@@ -74,7 +74,7 @@ void Model_AttributeIntArray::setValue(const int theIndex,
                                        const int theValue,
                                        bool sendUpdated)
 {
-  if (myArray->Value(theIndex) != theValue) {
+  if (!isInitialized() || myArray->Value(theIndex) != theValue) {
     setInitialized();
     myArray->SetValue(theIndex, theValue);
     if (sendUpdated)
index 3ee60386a8aeb4026a47ea5caf404b0a052b863f..ab7f06cf4a7ab1a2e399df573c84b3a7d215421c 100644 (file)
@@ -48,7 +48,8 @@ class Model_AttributeSelection : public ModelAPI_AttributeSelection,
   /// Reference to the parent attribute, if any (to split selection compounds in issue 1799)
   Model_AttributeSelectionList* myParent;
 
-  std::shared_ptr<Model_Document> myRestoreDocument; // current document to restore by name
+  /// current document to restore by name
+  std::shared_ptr<Model_Document> myRestoreDocument;
   /// If true attribute selects geometry instead of shape.
   bool myIsGeometricalSelection;
 
index f616eb290d86f27d058078c334b19b2c5c25fb98..bf05de24f4be00aee013be2588962f2c5c46150e 100644 (file)
@@ -21,6 +21,7 @@
 #include <ModelAPI_Events.h>
 
 #include <GeomAPI_Pnt2d.h>
+#include <GeomAPI_Shape.h>
 
 //#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_ShapesFailedMessage   =====
+ModelAPI_ShapesFailedMessage::ModelAPI_ShapesFailedMessage(const Events_ID theID,
+                                                               const void* theSender)
+  : Events_Message(theID, theSender)
+{}
+
+ModelAPI_ShapesFailedMessage::~ModelAPI_ShapesFailedMessage()
+{}
+
+void ModelAPI_ShapesFailedMessage::setShapes(const ListOfShape& theShapes)
+{
+  myShapes = theShapes;
+}
+
+const ListOfShape& ModelAPI_ShapesFailedMessage::shapes() const
+{
+  return myShapes;
+}
index c79ec94d007f30f3a42b8845a332cc5a5cec28b5..68af75b1b4acf3bff1ec651e78b4f172531ac338 100644 (file)
@@ -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_ShapesFailedMessage)
+static const char * EVENT_OPERATION_SHAPES_FAILED = "OperationShapesFailed";
+
 /// 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_ShapesFailedMessage : public Events_Message
+{
+public:
+  /// Creates an message
+  MODELAPI_EXPORT ModelAPI_ShapesFailedMessage(const Events_ID theID, const void* theSender = 0);
+  /// Default destructor
+  MODELAPI_EXPORT virtual ~ModelAPI_ShapesFailedMessage();
+  /// Static. Returns EventID of the message.
+  MODELAPI_EXPORT static Events_ID eventId()
+  {
+    return Events_Loop::eventByName(EVENT_OPERATION_SHAPES_FAILED);
+  }
+
+  /// Sets list of failed vertices
+  MODELAPI_EXPORT void setShapes(const std::list< std::shared_ptr<GeomAPI_Shape> >& theVertices);
+  /// Returns list of failed vertices
+  MODELAPI_EXPORT const std::list< std::shared_ptr<GeomAPI_Shape> >& shapes() const;
+
+private:
+  std::list< std::shared_ptr<GeomAPI_Shape> > myShapes;
+};
+
 #endif
index 927f8de32bf56c52889e93cc2c6f26dd134ea4ba..edf117fc2411057a64e5d3adfb72419760d48dda 100644 (file)
@@ -198,6 +198,11 @@ class ModelAPI_Feature : public ModelAPI_Object
   {
     return data()->refattr(theID);
   }
+  /// Returns the refattrlist attribute by the identifier
+  inline std::shared_ptr<ModelAPI_AttributeRefAttrList> refattrlist(const std::string& theID)
+  {
+    return data()->refattrlist(theID);
+  }
   /// Returns the reference attribute by the identifier
   inline std::shared_ptr<ModelAPI_AttributeReference> reference(const std::string& theID)
   {
index 764845fd38845c8a739b92c7d4d6a36b229ca034..f6bc05330f0a0ef8bd8d1043dfea744893ed8120 100644 (file)
@@ -422,18 +422,18 @@ Wire_39 = model.addWire(Part_1_doc, [model.selection("EDGE", "PartSet/Base/Sketc
 Pipe_5 = model.addPipe(Part_1_doc, [model.selection("WIRE", "Wire_39_1")], model.selection("EDGE", "PartSet/OZ"))
 Pipe_6 = model.addPipe(Part_1_doc, [model.selection("FACE", "Face_3_1")], model.selection("EDGE", "PartSet/OY"))
 Common_3 = model.addCommon(Part_1_doc, [model.selection("FACE", "Pipe_5_1"), model.selection("SOLID", "Pipe_6_1")])
-Filling_1 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_41&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_41&Wire_13_1/Wire_13_1")])
-Filling_2 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_7_2&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_7_2&Wire_13_1/Wire_13_1")])
-Filling_3 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_8_2&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_8_2&Wire_13_1/Wire_13_1")])
-Filling_4 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_5_2&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_5_2&Wire_13_1/Wire_13_1")])
-Filling_5 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_44&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_44&Wire_26_1/Wire_26_1")])
-Filling_6 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_6_2&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_6_2&Wire_26_1/Wire_26_1")])
-Filling_7 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_3_2&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_3_2&Wire_26_1/Wire_26_1")])
-Filling_8 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_2_2&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_2_2&Wire_26_1/Wire_26_1")])
-Filling_9 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_23&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_23&Wire_26_1/Wire_26_1")])
-Filling_10 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_1_2&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_1_2&Wire_26_1/Wire_26_1")])
-Filling_11 = model.addFilling(Part_1_doc, [model.selection("EDGE", "(Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_8_2&Wire_26_1/Wire_26_1)(Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_41&Wire_26_1/Wire_26_1)2(Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_1_2&Wire_26_1/Wire_26_1)2"), model.selection("EDGE", "(Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_8_2&Wire_13_1/Wire_13_1)(Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_41&Wire_13_1/Wire_13_1)2(Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_1_2&Wire_13_1/Wire_13_1)2")])
-Filling_12 = model.addFilling(Part_1_doc, [model.selection("EDGE", "(Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_1_2&Wire_26_1/Wire_26_1)(Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_8_2&Wire_26_1/Wire_26_1)2(Scale_2_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_23&Wire_26_1/Wire_26_1)2"), model.selection("EDGE", "(Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_1_2&Wire_13_1/Wire_13_1)(Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchArc_8_2&Wire_13_1/Wire_13_1)2(Scale_1_1/ME:Scaled&PartSet/OY/OY&PartSet/Shape/SketchLine_23&Wire_13_1/Wire_13_1)2")])
+Filling_1 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_9_1/Edge&Wire_22_1/Edge&Wire_35_1/Edge&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_9_1/Edge&Wire_22_1/Edge&Wire_35_1/Edge&Wire_13_1/Wire_13_1")])
+Filling_2 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_6_1/Edge&Wire_19_1/Edge&Wire_32_1/Edge&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_6_1/Edge&Wire_19_1/Edge&Wire_32_1/Edge&Wire_13_1/Wire_13_1")])
+Filling_3 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_10_1/Edge&Wire_23_1/Edge&Wire_36_1/Edge&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_10_1/Edge&Wire_23_1/Edge&Wire_36_1/Edge&Wire_13_1/Wire_13_1")])
+Filling_4 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_8_1/Edge&Wire_21_1/Edge&Wire_34_1/Edge&Wire_26_1/Wire_26_1"), model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_8_1/Edge&Wire_21_1/Edge&Wire_34_1/Edge&Wire_13_1/Wire_13_1")])
+Filling_5 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_7_1/Edge&Wire_20_1/Edge&Wire_33_1/Edge&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_7_1/Edge&Wire_20_1/Edge&Wire_33_1/Edge&Wire_26_1/Wire_26_1")])
+Filling_6 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_5_1/Edge&Wire_18_1/Edge&Wire_31_1/Edge&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_5_1/Edge&Wire_18_1/Edge&Wire_31_1/Edge&Wire_26_1/Wire_26_1")])
+Filling_7 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_4_1/Edge&Wire_17_1/Edge&Wire_30_1/Edge&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_4_1/Edge&Wire_17_1/Edge&Wire_30_1/Edge&Wire_26_1/Wire_26_1")])
+Filling_8 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_3_1/Edge&Wire_16_1/Edge&Wire_29_1/Edge&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_3_1/Edge&Wire_16_1/Edge&Wire_29_1/Edge&Wire_26_1/Wire_26_1")])
+Filling_9 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_2_1/Edge&Wire_15_1/Edge&Wire_28_1/Edge&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_2_1/Edge&Wire_15_1/Edge&Wire_28_1/Edge&Wire_26_1/Wire_26_1")])
+Filling_10 = model.addFilling(Part_1_doc, [model.selection("EDGE", "Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_1_1/Edge&Wire_14_1/Edge&Wire_27_1/Edge&Wire_13_1/Wire_13_1"), model.selection("EDGE", "Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_1_1/Edge&Wire_14_1/Edge&Wire_27_1/Edge&Wire_26_1/Wire_26_1")])
+Filling_11 = model.addFilling(Part_1_doc, [model.selection("EDGE", "(Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_10_1/Edge&Wire_23_1/Edge&Wire_36_1/Edge&Wire_26_1/Wire_26_1)(Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_9_1/Edge&Wire_22_1/Edge&Wire_35_1/Edge&Wire_26_1/Wire_26_1)2(Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_1_1/Edge&Wire_14_1/Edge&Wire_27_1/Edge&Wire_26_1/Wire_26_1)2"), model.selection("EDGE", "(Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_10_1/Edge&Wire_23_1/Edge&Wire_36_1/Edge&Wire_13_1/Wire_13_1)(Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_9_1/Edge&Wire_22_1/Edge&Wire_35_1/Edge&Wire_13_1/Wire_13_1)2(Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_1_1/Edge&Wire_14_1/Edge&Wire_27_1/Edge&Wire_13_1/Wire_13_1)2")])
+Filling_12 = model.addFilling(Part_1_doc, [model.selection("EDGE", "(Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_1_1/Edge&Wire_14_1/Edge&Wire_27_1/Edge&Wire_26_1/Wire_26_1)(Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_10_1/Edge&Wire_23_1/Edge&Wire_36_1/Edge&Wire_26_1/Wire_26_1)2(Scale_2_1/ME:Scaled&PartSet/OY/OY&Wire_2_1/Edge&Wire_15_1/Edge&Wire_28_1/Edge&Wire_26_1/Wire_26_1)2"), model.selection("EDGE", "(Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_1_1/Edge&Wire_14_1/Edge&Wire_27_1/Edge&Wire_13_1/Wire_13_1)(Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_10_1/Edge&Wire_23_1/Edge&Wire_36_1/Edge&Wire_13_1/Wire_13_1)2(Scale_1_1/ME:Scaled&PartSet/OY/OY&Wire_2_1/Edge&Wire_15_1/Edge&Wire_28_1/Edge&Wire_13_1/Wire_13_1)2")])
 Shell_1_objects = [model.selection("FACE", "Scale_1_1"), model.selection("FACE", "Scale_2_1"), model.selection("FACE", "Filling_1_1"), model.selection("FACE", "Filling_2_1"), model.selection("FACE", "Filling_3_1"), model.selection("FACE", "Filling_4_1"), model.selection("FACE", "Filling_5_1"), model.selection("FACE", "Filling_6_1"), model.selection("FACE", "Filling_7_1"), model.selection("FACE", "Filling_8_1"), model.selection("FACE", "Filling_9_1"), model.selection("FACE", "Filling_10_1"), model.selection("FACE", "Filling_11_1"), model.selection("FACE", "Filling_12_1")]
 Shell_1 = model.addShell(Part_1_doc, Shell_1_objects)
 Solid_1 = model.addSolid(Part_1_doc, [model.selection("SHELL", "Shell_1_1")])
index bd0368bbd9c34c71f3b0def8cd04e2851b31242e..2a9e22f9c3287926ca8671ea70b27d90614082fc 100644 (file)
@@ -243,7 +243,7 @@ Wire_1 = model.addWire(Part_1_doc, Wire_1_objects, False)
 Recover_1 = model.addRecover(Part_1_doc, Wire_1, [Extrusion_1.result().subResult(0), Extrusion_1.result().subResult(1)])
 Recover_1.result().setName("BaseHorizontale")
 Extrusion_2 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Recover_1_2/Modified_Face&Extrusion_1_1_1/To_Face")], model.selection(), 40, 10)
-Shell_1 = model.addShell(Part_1_doc, [model.selection("FACE", "_weak_name_8_Extrusion_2_1")])
+Shell_1 = model.addShell(Part_1_doc, [model.selection("FACE", "Extrusion_2_1/Generated_Face&Wire_1_1/Edge_16")])
 Recover_2 = model.addRecover(Part_1_doc, Shell_1, [Extrusion_2.result()])
 Recover_2.result().setName("BaseVerticale")
 Extrusion_3 = model.addExtrusion(Part_1_doc, [model.selection("SHELL", "Shell_1_1")], model.selection(), 0, 67)
index 80e23e406671da66ba31236338dc79c40582a32d..57538539ddf9727cd278c864f661fed8e88fd667 100644 (file)
@@ -31,68 +31,58 @@ SketchLine_2 = Sketch_1.addLine(20, 2.492755359431925e-15, 20, -31)
 SketchLine_3 = Sketch_1.addLine(-25.74772708486752, -20, -17.21320343559642, -20)
 SketchLine_4 = Sketch_1.addLine(-13.67766952966368, -21.46446609406726, 2.928932188134523, -38.07106781186547)
 SketchArc_1 = Sketch_1.addArc(-17.21320343559642, -25, -13.67766952966368, -21.46446609406726, -17.21320343559642, -20, False)
-SketchConstraintCoincidence_1 = Sketch_1.setCoincident(SketchArc_1.startPoint(), SketchLine_4.startPoint())
-SketchConstraintCoincidence_2 = Sketch_1.setCoincident(SketchArc_1.endPoint(), SketchLine_3.endPoint())
-SketchConstraintTangent_1 = Sketch_1.setTangent(SketchArc_1.results()[1], SketchLine_4.result())
-SketchConstraintTangent_2 = Sketch_1.setTangent(SketchArc_1.results()[1], SketchLine_3.result())
-SketchConstraintHorizontal_1 = Sketch_1.setHorizontal(SketchLine_3.result())
-SketchConstraintVertical_1 = Sketch_1.setVertical(SketchLine_2.result())
+Sketch_1.setCoincident(SketchArc_1.startPoint(), SketchLine_4.startPoint())
+Sketch_1.setCoincident(SketchArc_1.endPoint(), SketchLine_3.endPoint())
+Sketch_1.setTangent(SketchArc_1.results()[1], SketchLine_4.result())
+Sketch_1.setTangent(SketchArc_1.results()[1], SketchLine_3.result())
+Sketch_1.setHorizontal(SketchLine_3.result())
+Sketch_1.setVertical(SketchLine_2.result())
 SketchArc_2 = Sketch_1.addArc(0, 6.520200294596256e-23, -4.760492650097915, 19.42518236023452, 20, 2.492755359431925e-15, True)
-SketchConstraintCoincidence_3 = Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchArc_2.startPoint())
+Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchArc_2.startPoint())
 SketchArc_3 = Sketch_1.addArc(-12, -5, -29.79117622747561, -17.94117647058824, -17.23654191510763, 16.36770059625792, True)
-SketchConstraintCoincidence_4 = Sketch_1.setCoincident(SketchArc_3.endPoint(), SketchLine_1.startPoint())
-SketchConstraintCoincidence_4.setName("SketchConstraintCoincidence_5")
-SketchConstraintTangent_3 = Sketch_1.setTangent(SketchArc_3.results()[1], SketchLine_1.result())
-SketchConstraintCoincidence_5 = Sketch_1.setCoincident(SketchArc_2.endPoint(), SketchLine_2.startPoint())
-SketchConstraintCoincidence_5.setName("SketchConstraintCoincidence_6")
+Sketch_1.setCoincident(SketchArc_3.endPoint(), SketchLine_1.startPoint())
+Sketch_1.setTangent(SketchArc_3.results()[1], SketchLine_1.result())
+Sketch_1.setCoincident(SketchArc_2.endPoint(), SketchLine_2.startPoint())
 SketchArc_4 = Sketch_1.addArc(-25.74772708486752, -15, -29.79117622747561, -17.94117647058824, -25.74772708486752, -20, False)
-SketchConstraintCoincidence_6 = Sketch_1.setCoincident(SketchArc_4.startPoint(), SketchArc_3.startPoint())
-SketchConstraintCoincidence_6.setName("SketchConstraintCoincidence_7")
-SketchConstraintCoincidence_7 = Sketch_1.setCoincident(SketchArc_4.endPoint(), SketchLine_3.startPoint())
-SketchConstraintCoincidence_7.setName("SketchConstraintCoincidence_8")
-SketchConstraintTangent_4 = Sketch_1.setTangent(SketchArc_4.results()[1], SketchLine_3.result())
-SketchConstraintTangent_5 = Sketch_1.setTangent(SketchArc_4.results()[1], SketchArc_3.results()[1])
+Sketch_1.setCoincident(SketchArc_4.startPoint(), SketchArc_3.startPoint())
+Sketch_1.setCoincident(SketchArc_4.endPoint(), SketchLine_3.startPoint())
+Sketch_1.setTangent(SketchArc_4.results()[1], SketchLine_3.result())
+Sketch_1.setTangent(SketchArc_4.results()[1], SketchArc_3.results()[1])
 SketchArc_5 = Sketch_1.addArc(10, -31, 20, -31, 2.928932188134523, -38.07106781186547, True)
-SketchConstraintCoincidence_8 = Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchArc_5.startPoint())
-SketchConstraintCoincidence_8.setName("SketchConstraintCoincidence_9")
-SketchConstraintCoincidence_9 = Sketch_1.setCoincident(SketchArc_5.endPoint(), SketchLine_4.endPoint())
-SketchConstraintCoincidence_9.setName("SketchConstraintCoincidence_10")
-SketchConstraintTangent_6 = Sketch_1.setTangent(SketchArc_5.results()[1], SketchLine_4.result())
-SketchConstraintTangent_7 = Sketch_1.setTangent(SketchLine_2.result(), SketchArc_5.results()[1])
-SketchConstraintTangent_8 = Sketch_1.setTangent(SketchLine_1.result(), SketchArc_2.results()[1])
-SketchConstraintLength_1 = Sketch_1.setLength(SketchLine_2.result(), 31)
-SketchConstraintTangent_9 = Sketch_1.setTangent(SketchArc_2.results()[1], SketchLine_2.result())
-SketchConstraintAngle_1 = Sketch_1.setAngle(SketchLine_3.result(), SketchLine_4.result(), 135)
-SketchConstraintRadius_1 = Sketch_1.setRadius(SketchArc_1.results()[1], 5)
-SketchConstraintEqual_1 = Sketch_1.setEqual(SketchArc_4.results()[1], SketchArc_1.results()[1])
-SketchConstraintRadius_2 = Sketch_1.setRadius(SketchArc_5.results()[1], 10)
-SketchConstraintRadius_3 = Sketch_1.setRadius(SketchArc_2.results()[1], 20)
-SketchConstraintRadius_4 = Sketch_1.setRadius(SketchArc_3.results()[1], 22)
+Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchArc_5.startPoint())
+Sketch_1.setCoincident(SketchArc_5.endPoint(), SketchLine_4.endPoint())
+Sketch_1.setTangent(SketchArc_5.results()[1], SketchLine_4.result())
+Sketch_1.setTangent(SketchLine_2.result(), SketchArc_5.results()[1])
+Sketch_1.setTangent(SketchLine_1.result(), SketchArc_2.results()[1])
+Sketch_1.setLength(SketchLine_2.result(), 31)
+Sketch_1.setTangent(SketchArc_2.results()[1], SketchLine_2.result())
+Sketch_1.setAngle(SketchLine_3.result(), SketchLine_4.result(), 135)
+Sketch_1.setRadius(SketchArc_1.results()[1], 5)
+Sketch_1.setEqual(SketchArc_4.results()[1], SketchArc_1.results()[1])
+Sketch_1.setRadius(SketchArc_5.results()[1], 10)
+Sketch_1.setRadius(SketchArc_2.results()[1], 20)
+Sketch_1.setRadius(SketchArc_3.results()[1], 22)
 SketchCircle_1 = Sketch_1.addCircle(-22, -5, 7.5)
 SketchLine_5 = Sketch_1.addLine(-22, -5, -12, -5)
 SketchLine_5.setAuxiliary(True)
-SketchConstraintCoincidence_10 = Sketch_1.setCoincident(SketchCircle_1.center(), SketchLine_5.startPoint())
-SketchConstraintCoincidence_10.setName("SketchConstraintCoincidence_11")
-SketchConstraintCoincidence_11 = Sketch_1.setCoincident(SketchArc_3.center(), SketchLine_5.endPoint())
-SketchConstraintCoincidence_11.setName("SketchConstraintCoincidence_12")
-SketchConstraintHorizontal_2 = Sketch_1.setHorizontal(SketchLine_5.result())
-SketchConstraintRadius_5 = Sketch_1.setRadius(SketchCircle_1.results()[1], 7.5)
+Sketch_1.setCoincident(SketchCircle_1.center(), SketchLine_5.startPoint())
+Sketch_1.setCoincident(SketchArc_3.center(), SketchLine_5.endPoint())
+Sketch_1.setHorizontal(SketchLine_5.result())
+Sketch_1.setRadius(SketchCircle_1.results()[1], 7.5)
 SketchCircle_2 = Sketch_1.addCircle(0, 6.520200294596256e-23, 10)
-SketchConstraintCoincidence_12 = Sketch_1.setCoincident(SketchArc_2.center(), SketchCircle_2.center())
-SketchConstraintCoincidence_12.setName("SketchConstraintCoincidence_13")
-SketchConstraintRadius_6 = Sketch_1.setRadius(SketchCircle_2.results()[1], 10)
-SketchConstraintDistanceVertical_1 = Sketch_1.setVerticalDistance(SketchCircle_2.center(), SketchArc_3.center(), 5)
+Sketch_1.setCoincident(SketchArc_2.center(), SketchCircle_2.center())
+Sketch_1.setRadius(SketchCircle_2.results()[1], 10)
+Sketch_1.setVerticalDistance(SketchCircle_2.center(), SketchArc_3.center(), 5)
 SketchCircle_3 = Sketch_1.addCircle(7, -25, 7.5)
-SketchConstraintRadius_7 = Sketch_1.setRadius(SketchCircle_3.results()[1], 7.5)
-SketchConstraintDistanceHorizontal_1 = Sketch_1.setHorizontalDistance(SketchArc_2.center(), SketchCircle_3.center(), 7)
-SketchConstraintDistance_1 = Sketch_1.setDistance(SketchCircle_2.center(), SketchLine_3.result(), 20, True)
-SketchConstraintDistanceVertical_2 = Sketch_1.setVerticalDistance(SketchArc_2.center(), SketchCircle_3.center(), 25)
-SketchConstraintDistanceHorizontal_2 = Sketch_1.setHorizontalDistance(SketchLine_5.endPoint(), SketchArc_2.center(), 12)
-SketchConstraintDistanceHorizontal_3 = Sketch_1.setHorizontalDistance(SketchCircle_1.center(), SketchArc_2.center(), 22)
+Sketch_1.setRadius(SketchCircle_3.results()[1], 7.5)
+Sketch_1.setHorizontalDistance(SketchArc_2.center(), SketchCircle_3.center(), 7)
+Sketch_1.setDistance(SketchCircle_2.center(), SketchLine_3.result(), 20, True)
+Sketch_1.setVerticalDistance(SketchArc_2.center(), SketchCircle_3.center(), 25)
+Sketch_1.setHorizontalDistance(SketchLine_5.endPoint(), SketchArc_2.center(), 12)
+Sketch_1.setHorizontalDistance(SketchCircle_1.center(), SketchArc_2.center(), 22)
 SketchProjection_1 = Sketch_1.addProjection(model.selection("VERTEX", "PartSet/Origin"), False)
 SketchPoint_1 = SketchProjection_1.createdFeature()
-SketchConstraintCoincidence_13 = Sketch_1.setCoincident(SketchCircle_2.center(), SketchAPI_Point(SketchPoint_1).coordinates())
-SketchConstraintCoincidence_13.setName("SketchConstraintCoincidence_14")
+Sketch_1.setCoincident(SketchCircle_2.center(), SketchAPI_Point(SketchPoint_1).coordinates())
 model.do()
 Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchArc_3_2f-SketchArc_4_2f-SketchLine_3f-SketchArc_1_2r-SketchLine_4f-SketchArc_5_2f-SketchLine_2r-SketchArc_2_2f-SketchLine_1r-SketchCircle_1_2r-SketchCircle_2_2r-SketchCircle_3_2r")], model.selection(), 6.5, 0)
 Sketch_2 = model.addSketch(Part_1_doc, model.selection("FACE", "Extrusion_1_1/Generated_Face&Sketch_1/SketchLine_2"))
@@ -100,18 +90,14 @@ SketchLine_6 = Sketch_2.addLine(14.45330132699559, 5.092287733454298, -12.158140
 SketchLine_7 = Sketch_2.addLine(-12.15814035598059, 5.092287733454298, -12.15814035598059, 23.78152371848275)
 SketchLine_8 = Sketch_2.addLine(-12.15814035598059, 23.78152371848275, 14.45330132699559, 23.78152371848275)
 SketchLine_9 = Sketch_2.addLine(14.45330132699559, 23.78152371848275, 14.45330132699559, 5.092287733454298)
-SketchConstraintCoincidence_14 = Sketch_2.setCoincident(SketchLine_9.endPoint(), SketchLine_6.startPoint())
-SketchConstraintCoincidence_14.setName("SketchConstraintCoincidence_15")
-SketchConstraintCoincidence_15 = Sketch_2.setCoincident(SketchLine_6.endPoint(), SketchLine_7.startPoint())
-SketchConstraintCoincidence_15.setName("SketchConstraintCoincidence_16")
-SketchConstraintCoincidence_16 = Sketch_2.setCoincident(SketchLine_7.endPoint(), SketchLine_8.startPoint())
-SketchConstraintCoincidence_16.setName("SketchConstraintCoincidence_17")
-SketchConstraintCoincidence_17 = Sketch_2.setCoincident(SketchLine_8.endPoint(), SketchLine_9.startPoint())
-SketchConstraintCoincidence_17.setName("SketchConstraintCoincidence_18")
-SketchConstraintHorizontal_3 = Sketch_2.setHorizontal(SketchLine_6.result())
-SketchConstraintVertical_2 = Sketch_2.setVertical(SketchLine_7.result())
-SketchConstraintHorizontal_4 = Sketch_2.setHorizontal(SketchLine_8.result())
-SketchConstraintVertical_3 = Sketch_2.setVertical(SketchLine_9.result())
+Sketch_2.setCoincident(SketchLine_9.endPoint(), SketchLine_6.startPoint())
+Sketch_2.setCoincident(SketchLine_6.endPoint(), SketchLine_7.startPoint())
+Sketch_2.setCoincident(SketchLine_7.endPoint(), SketchLine_8.startPoint())
+Sketch_2.setCoincident(SketchLine_8.endPoint(), SketchLine_9.startPoint())
+Sketch_2.setHorizontal(SketchLine_6.result())
+Sketch_2.setVertical(SketchLine_7.result())
+Sketch_2.setHorizontal(SketchLine_8.result())
+Sketch_2.setVertical(SketchLine_9.result())
 model.do()
 ExtrusionCut_1 = model.addExtrusionCut(Part_1_doc, [model.selection("FACE", "Sketch_2/Face-SketchLine_9r-SketchLine_8r-SketchLine_7r-SketchLine_6r")], model.selection(), 0, 100, [model.selection("SOLID", "Extrusion_1_1")])
 model.do()
index 6c7101b3985bd6ff39e729f8dc30cea5b99329ea..778261db1f80229da20d80fcc35681e198b9d9ba 100644 (file)
@@ -54,6 +54,9 @@
 // directors
 %feature("director") ModelHighAPI_Dumper;
 
+// renamed methods
+%rename(__print__) ModelHighAPI_Dumper::operator<<;
+
 // shared pointers
 %shared_ptr(ModelHighAPI_Interface)
 %shared_ptr(ModelHighAPI_Folder)
index 15d10c6aa7c01e9678042c848c0c4a20021663bc..636726edabedf10991cdd2bfd863b1e51553a6c0 100644 (file)
@@ -569,12 +569,16 @@ static int toInt(const std::string& theString)
 
 const std::string& ModelHighAPI_Dumper::name(const EntityPtr& theEntity,
                                              bool theSaveNotDumped,
-                                             bool theUseEntityName)
-{
-  EntityNameMap::const_iterator aFound = myNames.find(theEntity);
-  if (aFound != myNames.end())
+                                             bool theUseEntityName,
+                                             bool theSetIsDumped)
+{
+  EntityNameMap::iterator aFound = myNames.find(theEntity);
+  if (aFound != myNames.end()) {
+    // Set dumped flag for postponed constraints which are without names
+    if (!aFound->second.myIsDumped)
+      aFound->second.myIsDumped = theSetIsDumped;
     return aFound->second.myCurrentName;
-
+  }
   // entity is not found, store it
   std::string aName, aKind;
   bool isDefaultName = false;
@@ -650,6 +654,8 @@ const std::string& ModelHighAPI_Dumper::name(const EntityPtr& theEntity,
   if (aFeature)
     saveResultNames(aFeature);
 
+  myNames[theEntity].myIsDumped = theSetIsDumped;
+
   return myNames[theEntity].myCurrentName;
 }
 
@@ -771,7 +777,7 @@ bool ModelHighAPI_Dumper::process(const std::shared_ptr<ModelAPI_CompositeFeatur
     // dump features in the document
     bool aRes = process(aSubDoc);
     if (isDumpModelDo)
-      *this << "model.do()\n";
+      *this << "\nmodel.do()\n";
     *this << std::endl;
     return aRes;
   }
@@ -1053,6 +1059,16 @@ bool ModelHighAPI_Dumper::isDefaultTransparency(const ResultPtr& theResult) cons
   return fabs(anAttribute->value()) < 1.e-12;
 }
 
+bool ModelHighAPI_Dumper::dumpCommentBeforeFeature(const FeaturePtr& theFeature) const
+{
+  // currently, the comment should not be dumped only before the filters
+  FiltersFeaturePtr aFilters = std::dynamic_pointer_cast<ModelAPI_FiltersFeature>(theFeature);
+  if (aFilters)
+    return false;
+  // all other features should be commented before the dump
+  return true;
+}
+
 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const char theChar)
 {
   *myDumpStorage << theChar;
@@ -1446,10 +1462,10 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
     const std::shared_ptr<ModelAPI_AttributeSelectionList>& theAttrSelList)
 {
   static const int aThreshold = 2;
-  static bool aDumpAsIs = false;
+  static int aNbSpaces = 0;
   // if number of elements in the list if greater than a threshold,
   // dump it in a separate line with specific name
-  if (aDumpAsIs || theAttrSelList->size() <= aThreshold) {
+  if (aNbSpaces > 0 || theAttrSelList->size() <= aThreshold) {
     *myDumpStorage << "[";
 
     GeomShapePtr aShape;
@@ -1472,6 +1488,11 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
 
       if(isAdded) {
         *myDumpStorage << ", ";
+        // print each attribute on separate line with the appropriate shift
+        if (aNbSpaces > 0) {
+          std::string aSpaces(aNbSpaces + 1, ' ');
+          *myDumpStorage << "\n" << aSpaces;
+        }
       } else {
         isAdded = true;
       }
@@ -1505,9 +1526,9 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
     }
     // reserve dumped buffer and store list "as is"
     myDumpStorage->reserveBuffer();
-    aDumpAsIs = true;
+    aNbSpaces = (int)aListName.size() + 3;
     *this << aListName << " = " << theAttrSelList << "\n";
-    aDumpAsIs = false;
+    aNbSpaces = 0;
     // append reserved data to the end of the current buffer
     myDumpStorage->restoreReservedBuffer();
     *myDumpStorage << aListName;
@@ -1532,6 +1553,11 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
   return *this;
 }
 
+void ModelHighAPI_Dumper::newline()
+{
+  *this << std::endl;
+}
+
 /// Dump std::endl
 ModelHighAPI_Dumper& operator<<(ModelHighAPI_Dumper& theDumper,
                                 std::basic_ostream<char>& (*theEndl)(std::basic_ostream<char>&))
@@ -1561,6 +1587,7 @@ ModelHighAPI_Dumper& operator<<(ModelHighAPI_Dumper& theDumper,
 
   // store all not-dumped entities first
   std::set<EntityPtr> aNotDumped = theDumper.myNotDumpedEntities;
+  theDumper.clearNotDumped();
   theDumper.myDumpStorage->reserveBuffer();
   std::set<EntityPtr>::const_iterator anIt = aNotDumped.begin();
   for (; anIt != aNotDumped.end(); ++anIt) {
index 46eee3ff829c1ce98c4038114b035b89c3480102..5a0461f052938887202f359916d124f9d0825af9 100644 (file)
@@ -183,7 +183,7 @@ public:
   /// \return name of the entity
   MODELHIGHAPI_EXPORT
   const std::string& name(const EntityPtr& theEntity, bool theSaveNotDumped = true,
-                          bool theUseEntityName = false);
+                          bool theUseEntityName = false, bool theSetIsDumped = false);
 
   /// Returns name of parent composite feature for specified entity
   MODELHIGHAPI_EXPORT
@@ -245,6 +245,9 @@ public:
   ModelHighAPI_Dumper& operator<<(ModelHighAPI_Dumper& theDumper,
                                 std::basic_ostream<char>& (*theEndl)(std::basic_ostream<char>&));
 
+  /// Print std::endl from Python
+  MODELHIGHAPI_EXPORT void newline();
+
   /// Dump GeomAPI_Pnt in the following form:
   /// "GeomAPI_Pnt(X, Y, Z)"
   MODELHIGHAPI_EXPORT
@@ -355,6 +358,10 @@ public:
   MODELHIGHAPI_EXPORT virtual void exportVariable(const std::string& /*theEntry*/,
                                                   const std::string& /*theVarName*/) const {}
 
+  /// Returns \c true if the special comment line to improve the readability
+  /// should be printed before the feature of the given kind.
+  MODELHIGHAPI_EXPORT bool dumpCommentBeforeFeature(const FeaturePtr& theFeature) const;
+
 protected:
   /// Dump "setName" command if last entity had user-defined name
   MODELHIGHAPI_EXPORT void dumpEntitySetName();
index 198a0906526dc27432a232a7c0d9b5a386d18476..bf7a6fa9e39aa8c179b2b169d409f5eb45f0ac3d 100644 (file)
@@ -287,8 +287,11 @@ std::string ModelHighAPI_FeatureStore::dumpAttr(const AttributePtr& theAttr) {
         if (isSketchFeatures) {
           // do not control construction features of an ellipse and other
           FeaturePtr aFeature = ModelAPI_Feature::feature(*aL);
-          if (aFeature->getKind() == "SketchConstraintCoincidenceInternal")
-            continue; // skip internal constraints
+          //if (aFeature->getKind() == "SketchConstraintCoincidenceInternal")
+          //  continue; // skip internal constraints
+          std::string aStr = aFeature->getKind().substr(0, 16);
+          if (aStr == "SketchConstraint")
+            continue; // no need to dump and check constraints
         }
         aResList.push_back((*aL)->data()->name());
       } else if (!isSketchFeatures) {
index accf0c79fc93fe10a88f1107454691ccae2685c7..333de514e0347ea08358160a39ab93727b500db7 100644 (file)
@@ -50,7 +50,7 @@
 // Used in INTERFACE_N for create variable and getter
 #define DEFINE_ATTRIBUTE(NAME, TYPE, COMMENT) \
     COMMENT \
-    std::shared_ptr<TYPE> NAME() const { return VAR_NAME(NAME); } \
+    virtual std::shared_ptr<TYPE> NAME() const { return VAR_NAME(NAME); } \
   protected: \
     std::shared_ptr<TYPE> VAR_NAME(NAME); \
   public:
index 4b4b689e31711c5ab41d698d8a6deec1fb151f55..0b91369bf71167a38dfbe109721542d3f2aade6f 100644 (file)
@@ -431,9 +431,13 @@ std::string storeFeatures(const std::string& theDocName, DocumentPtr theDoc,
   for(; allIter != allObjects.end(); allIter++) {
     ObjectPtr anObject = *allIter;
     FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(anObject);
-    if (aFeature && aFeature->getKind() == "SketchConstraintCoincidenceInternal")
-      continue; // no need to dump and check internal constraints
+    //if (aFeature && aFeature->getKind() == "SketchConstraintCoincidenceInternal")
 
+    if (aFeature) {
+      std::string aStr = aFeature->getKind().substr(0, 16);
+      if (aStr == "SketchConstraint")
+        continue; // no need to dump and check constraints
+    }
     if (theCompare) {
       std::map<std::string, ModelHighAPI_FeatureStore>::iterator
         anObjFind = aDocFind->second.find(anObject->data()->name());
index 267d2d4cf19454094066a0c2be52a5682e166ac2..f821c793f7df970e045e174f3a8e82418a8a6dc2 100644 (file)
@@ -107,7 +107,8 @@ bool ModuleBase_PagedContainer::restoreValueCustom()
       aCaseId = QString::fromStdString(aStringAttr->value());
     else
       aCaseId = QString::fromStdString(aDefVal.empty() ? aStringAttr->value() : aDefVal);
-    myIsFirst = false;
+    if (myIsFirst)
+      storeValueCustom();
     int idx = myCaseIds.indexOf(aCaseId);
     if (idx == -1)
       idx = currentPageIndex();
index 656bb73099780db3458f8210fdb386f505559042..63cb21479afb56b651be2604709a92c97909b45f 100644 (file)
@@ -54,6 +54,7 @@ ModuleBase_WidgetOptionalBox::ModuleBase_WidgetOptionalBox(QWidget* theParent,
 
   myHaveFrame = theData->getBooleanAttribute("has_frame", true);
   myEnableOnCheck = theData->getBooleanAttribute("enable_on_check", true);
+  myAlwaysShowTitle = theData->getBooleanAttribute("show_title", false);
 
   bool isChecked = theData->getBooleanAttribute(ATTR_DEFAULT, false);
   setDefaultValue(isChecked ? "true" : "false");
@@ -201,7 +202,10 @@ void ModuleBase_WidgetOptionalBox::createControl(const OptionType& theType)
 
     myCheckBoxLayout = new QHBoxLayout(myCheckBoxFrame);
     ModuleBase_Tools::adjustMargins(myCheckBoxLayout);
-    myCheckBox = new QCheckBox(myCheckBoxFrame);
+    if (myAlwaysShowTitle)
+      myCheckBox = new QCheckBox(translate(myGroupTitle), myCheckBoxFrame);
+    else
+      myCheckBox = new QCheckBox(myCheckBoxFrame);
     myCheckBox->setChecked(getDefaultValue() == "true");
     myCheckBoxLayout->addWidget(myCheckBox);
 
index 620fe6fe7fac639644038022c1fbef2922acafee..e819d73b9a5ff0f1a21c2e7e06d943f1f5abdc9b 100644 (file)
@@ -136,6 +136,7 @@ private:
 
   bool myHaveFrame;
   bool myEnableOnCheck;
+  bool myAlwaysShowTitle;
 };
 
 #endif /* ModuleBase_WidgetOptionalBox_H_ */
index 5c6da879747c7780f952fabf4be6ddf92e1c0bd2..aa76b42ec4a8d97cbb942ab2943f623c006cc2de 100644 (file)
 #include <ModuleBase_WidgetRadiobox.h>
 #include <ModuleBase_PageBase.h>
 
+#include <Config_WidgetAPI.h>
+
 #include <QFormLayout>
 #include <QRadioButton>
 #include <QFrame>
 #include <QButtonGroup>
 
-
 ModuleBase_WidgetRadiobox::ModuleBase_WidgetRadiobox(QWidget* theParent,
                                                      const Config_WidgetAPI* theData)
   : ModuleBase_PagedContainer(theParent, theData)
@@ -35,6 +36,8 @@ ModuleBase_WidgetRadiobox::ModuleBase_WidgetRadiobox(QWidget* theParent,
   myGroup = new QButtonGroup(this);
   myGroup->setExclusive(true);
 
+  myVerticalAlignment = theData->getProperty("align_subs").find("vert") == 0;
+
   connect(myGroup, SIGNAL(buttonToggled(int, bool)), SLOT(onPageChanged()));
 }
 
@@ -68,7 +71,11 @@ int ModuleBase_WidgetRadiobox::addPage(ModuleBase_PageBase* thePage,
 
   //QFrame* aFrame = dynamic_cast<QFrame*>(thePage);
   QWidget* aPage = thePage->pageWidget();
-  myLayout->addRow(aWgt, aPage);
+  if (myVerticalAlignment) {
+    myLayout->addRow(aWgt);
+    myLayout->addRow(aPage);
+  } else
+    myLayout->addRow(aWgt, aPage);
   myGroup->addButton(aButton, myGroup->buttons().count());
 
   bool isDefault = theCaseId.toStdString() == getDefaultValue();
index ef35a0f960604ea1ad0b9f44962527f615d3ccdd..cffb45a2120f82d1313523263ed2970068a8f100 100644 (file)
@@ -59,6 +59,7 @@ protected:
 private:
   QFormLayout* myLayout;
   QButtonGroup* myGroup;
+  bool myVerticalAlignment;
 };
 
 #endif
\ No newline at end of file
index c61227cb0569151e04e617d4db6dd22439d4c7a7..fc0fb74d4e1088e4f02c78698dbe5de81a8d95aa 100644 (file)
@@ -37,6 +37,7 @@
 #include <Config_PropManager.h>
 #include <Events_Loop.h>
 #include <ModelAPI_Events.h>
+#include <GeomAlgoAPI_CompoundBuilder.h>
 
 #include <AIS_InteractiveContext.hxx>
 #include <AIS_InteractiveObject.hxx>
@@ -50,6 +51,7 @@ PartSet_CustomPrs::PartSet_CustomPrs(ModuleBase_IWorkshop* theWorkshop)
 {
   Events_Loop* aLoop = Events_Loop::loop();
   aLoop->registerListener(this, Events_Loop::eventByName(EVENT_EMPTY_OPERATION_PRESENTATION));
+  aLoop->registerListener(this, Events_Loop::eventByName(EVENT_OPERATION_SHAPES_FAILED));
 
   initPresentation(ModuleBase_IModule::CustomizeArguments);
   initPresentation(ModuleBase_IModule::CustomizeResults);
@@ -99,6 +101,8 @@ bool PartSet_CustomPrs::deactivate(const ModuleBase_IModule::ModuleBase_Customiz
 {
   myIsActive[theFlag] = false;
   erasePresentation(theFlag, theUpdateViewer);
+  if (theFlag == ModuleBase_IModule::CustomizeResults)
+    clearErrorShape();
   return true;
 }
 
@@ -244,12 +248,53 @@ void PartSet_CustomPrs::clearPrs()
   clearPresentation(ModuleBase_IModule::CustomizeArguments);
   clearPresentation(ModuleBase_IModule::CustomizeResults);
   clearPresentation(ModuleBase_IModule::CustomizeHighlightedObjects);
+  clearErrorShape();
+}
+
+void PartSet_CustomPrs::clearErrorShape()
+{
+  if (!myErrorShapes.IsNull()) {
+    Handle(AIS_InteractiveContext) aContext = myWorkshop->viewer()->AISContext();
+    if (aContext->IsDisplayed(myErrorShapes)) {
+      aContext->Remove(myErrorShapes, true);
+      myErrorShapes.Nullify();
+    }
+  }
 }
 
 void PartSet_CustomPrs::processEvent(const std::shared_ptr<Events_Message>& theMessage)
 {
   if (theMessage->eventID() == Events_Loop::eventByName(EVENT_EMPTY_OPERATION_PRESENTATION))
     myPresentationIsEmpty = true; /// store state to analize it after display/erase is finished
+  else if (theMessage->eventID() == Events_Loop::eventByName(EVENT_OPERATION_SHAPES_FAILED)) {
+    std::shared_ptr<ModelAPI_ShapesFailedMessage> aErrMsg =
+      std::dynamic_pointer_cast<ModelAPI_ShapesFailedMessage>(theMessage);
+    Handle(AIS_InteractiveContext) aContext = myWorkshop->viewer()->AISContext();
+    ListOfShape aShapes = aErrMsg->shapes();
+    if (aShapes.size() > 0) {
+      GeomShapePtr aCompound = GeomAlgoAPI_CompoundBuilder::compound(aShapes);
+      TopoDS_Shape aErrShape = aCompound->impl<TopoDS_Shape>();
+      if (myErrorShapes.IsNull()) {
+        myErrorShapes = new AIS_Shape(aErrShape);
+        myErrorShapes->SetColor(Quantity_NOC_RED);
+        Handle(Prs3d_Drawer) aDrawer = myErrorShapes->Attributes();
+        aDrawer->SetPointAspect(new Prs3d_PointAspect(Aspect_TOM_RING1, Quantity_NOC_RED, 2.));
+        aDrawer->SetLineAspect(new Prs3d_LineAspect(Quantity_NOC_RED, Aspect_TOL_SOLID, 2.));
+        aContext->Display(myErrorShapes, true);
+        aContext->Deactivate(myErrorShapes);
+      }
+      else {
+        myErrorShapes->Set(aErrShape);
+        aContext->Redisplay(myErrorShapes, true);
+      }
+    }
+    else {
+      if (!myErrorShapes.IsNull()) {
+        aContext->Remove(myErrorShapes, true);
+        myErrorShapes.Nullify();
+      }
+    }
+  }
 }
 
 void PartSet_CustomPrs::initPresentation(
index 11f635666a5f1dddb44029dccfe46d29fe86e42a..117af4a14cefcd6dcaf8d858f56c4f5b1874abc4 100644 (file)
@@ -151,6 +151,9 @@ private:
   /// \return theShapeColor a color
   Quantity_Color getShapeColor(const ModuleBase_IModule::ModuleBase_CustomizeFlag& theFlag);
 
+  /// Removes error shapes presentation
+  void clearErrorShape();
+
 private:
   ModuleBase_IWorkshop* myWorkshop; /// current workshop
   FeaturePtr myFeature; /// Reference to a feature object
@@ -161,6 +164,8 @@ private:
   QMap<ModuleBase_IModule::ModuleBase_CustomizeFlag, bool> myIsActive;
 
   int myDisabledMode;
+
+  Handle(AIS_Shape) myErrorShapes;
 };
 
 #endif
index 7ea68ddfd93f482889bd67beb730e0dcb5413a3e..446b253b31c064f3380bb860c37458d7bbafe409 100644 (file)
 
 #include <SelectMgr_ListIteratorOfListOfFilter.hxx>
 
+#define FEATURE_ITEM_COLOR "0,0,225"
+
+
 /*!Create and return new instance of XGUI_Module*/
 extern "C" PARTSET_EXPORT ModuleBase_IModule* createModule(ModuleBase_IWorkshop* theWshop)
 {
@@ -248,6 +251,10 @@ PartSet_Module::PartSet_Module(ModuleBase_IWorkshop* theWshop)
     "Dimension color",
     Config_Prop::Color, SKETCH_DIMENSION_COLOR);
 
+  Config_PropManager::registerProp("Visualization", "feature_objectbrowser_color",
+    "Feature items in Object Browser",
+    Config_Prop::Color, FEATURE_ITEM_COLOR);
+
   Config_PropManager::registerProp("Shortcuts", "add_parameter_shortcut",
     "Add parameter in parameters manager dialog",
     Config_Prop::Shortcut, "Ctrl+A");
index 24b4134cc48bbe16e7c5ada3138c60cbd58c66af..cd6b78cbd559d6651e70bef7ae44ae41e6a0b570 100644 (file)
@@ -243,24 +243,20 @@ void PartSet_OperationPrs::appendShapeIfVisible(ModuleBase_IWorkshop* theWorksho
                               GeomShapePtr theGeomShape,
                               QMap<ObjectPtr, QList<GeomShapePtr> >& theObjectShapes)
 {
-  //XGUI_Displayer* aDisplayer = XGUI_Tools::workshop(theWorkshop)->displayer();
-  //// VSV: Do not use isVisible checking because it can be used when state "Show Only" is ON
-  //if (XGUI_Displayer::isVisible(aDisplayer, theObject)) {
-    if (theGeomShape.get()) {
-      if (theObjectShapes.contains(theObject))
-        theObjectShapes[theObject].append(theGeomShape);
-      else {
-        QList<GeomShapePtr> aShapes;
-        aShapes.append(theGeomShape);
-        theObjectShapes[theObject] = aShapes;
-      }
-    } else {
-  #ifdef DEBUG_EMPTY_SHAPE
-      qDebug(QString("Empty shape in result, result: %1")
-              .arg(ModuleBase_Tools::objectInfo(theObject)).toStdString().c_str());
-  #endif
+  if (theGeomShape.get()) {
+    if (theObjectShapes.contains(theObject))
+      theObjectShapes[theObject].append(theGeomShape);
+    else {
+      QList<GeomShapePtr> aShapes;
+      aShapes.append(theGeomShape);
+      theObjectShapes[theObject] = aShapes;
     }
-  //}
+  } else {
+#ifdef DEBUG_EMPTY_SHAPE
+    qDebug(QString("Empty shape in result, result: %1")
+            .arg(ModuleBase_Tools::objectInfo(theObject)).toStdString().c_str());
+#endif
+  }
 }
 
 void PartSet_OperationPrs::getFeatureShapes(const FeaturePtr& theFeature,
index b1095873f46ead0e9b88b0e585019c5a97b8cdd7..689c034b591fbe42854076736793db0ae6291a38 100644 (file)
@@ -504,6 +504,10 @@ void PartSet_SketcherMgr::onMousePressed(ModuleBase_IViewWindow* theWnd, QMouseE
       myPreviousDrawModeEnabled = aViewer->enableDrawMode(false);
       if (isRelaunchEditing)
         launchEditing();
+      else {
+        if (myCurrentSelection.size() > 1)
+          aFOperation->propertyPanel()->cleanContent();
+      }
       myIsEditLaunching = aPrevLaunchingState;
       if (aFeature.get() != NULL) {
         std::shared_ptr<SketchPlugin_Feature> aSketchFeature =
@@ -2249,7 +2253,7 @@ bool isIncludeToResult(const ObjectPtr& theObject)
   for (aIt = aRefsToMe.cbegin(); aIt != aRefsToMe.cend(); ++aIt) {
     if ((*aIt)->id() == SketchPlugin_Projection::PROJECTED_FEATURE_ID()) {
       FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>((*aIt)->owner());
-      if (aFeature.get()) {
+      if (aFeature.get() && !aFeature->isMacro()) {
         anAttr = aFeature->data()->boolean(SketchPlugin_Projection::INCLUDE_INTO_RESULT());
         if (anAttr.get())
           return anAttr->value();
@@ -2345,7 +2349,7 @@ void PartSet_SketcherMgr::customizeSketchPresentation(const ObjectPtr& theObject
     thePrs->setWidth(17);
     //  thePrs->setPointMarker(1, 1.); // Set point as a '+' symbol
   }
-  if (isCopy(aFeature) && !isIncludeToResult(aFeature)) {
+  if (isCopy(aFeature) || !isIncludeToResult(aFeature)) {
     double aPrsWidth = thePrs->width();
     thePrs->setWidth(aPrsWidth / 2.5);
   }
index 723bef070ede5a6b0abd4b501a49b8815af78326..378de5ace207f930864e8c32fd3b7c00f2f9d1b4 100644 (file)
@@ -240,6 +240,8 @@ private:
   std::shared_ptr<GeomAPI_Pnt2d> myClickedSketchPoint; /// cashed clicked point
 
   bool myIsAutoConstraints;
+  void* myLastAutoConstraint; //< Stores address of last automatic constraint.
+                              //< Cannot be used as a pointer!!!
 };
 
 #endif
index c63a5eb3a7fb4a7faec1681a457544a002a33d9b..c0400d6f74f356aa8abe83c655162f062a9b1b95 100644 (file)
@@ -40,6 +40,8 @@
 #include <ModelAPI_Folder.h>
 #include <ModelAPI_AttributeReference.h>
 
+#include <Config_PropManager.h>
+
 #include <QBrush>
 #include <QMap>
 
@@ -129,6 +131,13 @@ QVariant PartSet_ObjectNode::data(int theColumn, int theRole) const
       else
         return QIcon();
     }
+  case Qt::ForegroundRole:
+    if (myObject->groupName() == ModelAPI_Feature::group()) {
+      std::vector<int> aColor =
+        Config_PropManager::color("Visualization", "feature_objectbrowser_color");
+      return QColor(aColor[0], aColor[1], aColor[2]);
+    }
+    break;
   }
   return PartSet_TreeNode::data(theColumn, theRole);
 }
@@ -1199,7 +1208,16 @@ QTreeNodesList PartSet_ObjectFolderNode::objectsDeleted(const DocumentPtr& /*the
   return aResult;
 }
 
+QVariant PartSet_ObjectFolderNode::data(int theColumn, int theRole) const
+{
+  if (theRole == Qt::ForegroundRole) {
+    std::vector<int> aColor =
+      Config_PropManager::color("Visualization", "feature_objectbrowser_color");
+    return QColor(aColor[0], aColor[1], aColor[2]);
+  }
+  return PartSet_ObjectNode::data(theColumn, theRole);
 
+}
 
 //////////////////////////////////////////////////////////////////////////////////
 QVariant PartSet_StepNode::data(int theColumn, int theRole) const
index ef4ae8f8223acb1390eddedc3db06ac08dbf56b8..e5f3599a112240dcb49cea7d4bba33b6503af1ea 100644 (file)
@@ -325,6 +325,9 @@ public:
   /// \param theDoc a document where objects were deleted
   /// \param theGroup a name of group where objects were deleted
   virtual QTreeNodesList objectsDeleted(const DocumentPtr& theDoc, const QString& theGroup);
+
+  /// Returns the node representation according to theRole.
+  virtual QVariant data(int theColumn, int theRole) const;
 };
 
 
index 9365adc575d8067ce1476cc03c8c32d7ec47f706..83b82a90449fa7f1fb4736a659c6da9da1511ede 100644 (file)
@@ -158,15 +158,20 @@ myIsSelection(false)
   ModuleBase_Tools::zeroMargins(aLayout);
 
   QGroupBox* aViewBox = new QGroupBox(tr("Sketcher plane"), this);
-  QVBoxLayout* aViewLayout = new QVBoxLayout(aViewBox);
+  QGridLayout* aViewLayout = new QGridLayout(aViewBox);
 
   myViewInverted = new QCheckBox(tr("Reversed"), aViewBox);
-  aViewLayout->addWidget(myViewInverted);
+  aViewLayout->addWidget(myViewInverted, 0, 0);
+
+  myViewVisible = new QCheckBox(tr("Visible"), aViewBox);
+  myViewVisible->setChecked(true);
+  aViewLayout->addWidget(myViewVisible, 0, 1, Qt::AlignRight);
+  connect(myViewVisible, SIGNAL(toggled(bool)), this, SLOT(onShowViewPlane(bool)));
 
   QPushButton* aSetViewBtn =
     new QPushButton(QIcon(":icons/plane_view.png"), tr("Set plane view"), aViewBox);
   connect(aSetViewBtn, SIGNAL(clicked(bool)), this, SLOT(onSetPlaneView()));
-  aViewLayout->addWidget(aSetViewBtn);
+  aViewLayout->addWidget(aSetViewBtn, 1, 0, 1, 2);
 
   aLayout->addWidget(aViewBox);
 
@@ -313,7 +318,7 @@ bool PartSet_WidgetSketchLabel::setSelectionInternal(
   bool aDone = false;
   if (theValues.empty()) {
     // In order to make reselection possible, set empty object and shape should be done
-    setSelectionCustom(std::shared_ptr<ModuleBase_ViewerPrs>(
+    setSelectionCustom(ModuleBase_ViewerPrsPtr(
                               new ModuleBase_ViewerPrs(ObjectPtr(), GeomShapePtr(), NULL)));
     aDone = false;
   }
@@ -360,7 +365,8 @@ void PartSet_WidgetSketchLabel::updateByPlaneSelected(const ModuleBase_ViewerPrs
     CompositeFeaturePtr aSketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
     aModule->sketchMgr()->previewSketchPlane()->setSizeOfView(aSizeOfView, isSetSizeOfView);
     // Call of createSketchPlane is managed by events Loop
-    //aModule->sketchMgr()->previewSketchPlane()->createSketchPlane(aSketch, myWorkshop);
+    //if (myViewVisible->isChecked())
+    //  aModule->sketchMgr()->previewSketchPlane()->createSketchPlane(aSketch, myWorkshop);
   }
   // 2. if the planes were displayed, change the view projection
 
@@ -733,7 +739,7 @@ void PartSet_WidgetSketchLabel::onSetPlaneView()
 
 
 //******************************************************
-QList<std::shared_ptr<ModuleBase_ViewerPrs>> PartSet_WidgetSketchLabel::findCircularEdgesInPlane()
+QList<ModuleBase_ViewerPrsPtr> PartSet_WidgetSketchLabel::findCircularEdgesInPlane()
 {
   QList<std::shared_ptr<ModuleBase_ViewerPrs>> aResult;
   XGUI_Workshop* aWorkshop = XGUI_Tools::workshop(myWorkshop);
@@ -755,7 +761,7 @@ QList<std::shared_ptr<ModuleBase_ViewerPrs>> PartSet_WidgetSketchLabel::findCirc
             bool isContains = false;
             // Check that edge is not used.
             // It is possible that the same edge will be taken from different faces
-            foreach(std::shared_ptr<ModuleBase_ViewerPrs> aPrs, aResult) {
+            foreach(ModuleBase_ViewerPrsPtr aPrs, aResult) {
               GeomAPI_Edge aUsedEdge(aPrs->shape());
               if (aUsedEdge.isEqual(aEdgeShape)) {
                 isContains = true;
@@ -763,8 +769,7 @@ QList<std::shared_ptr<ModuleBase_ViewerPrs>> PartSet_WidgetSketchLabel::findCirc
               }
             }
             if (!isContains) {
-              std::shared_ptr<ModuleBase_ViewerPrs>
-                aPrs(new ModuleBase_ViewerPrs(aResObj, aEdgeShape));
+              ModuleBase_ViewerPrsPtr aPrs(new ModuleBase_ViewerPrs(aResObj, aEdgeShape));
               aResult.append(aPrs);
             }
           }
@@ -880,4 +885,17 @@ bool PartSet_WidgetSketchLabel::eventFilter(QObject* theObj, QEvent* theEvent)
       onShowPanel();
   }
   return ModuleBase_WidgetValidated::eventFilter(theObj, theEvent);
-}
\ No newline at end of file
+}
+
+void PartSet_WidgetSketchLabel::onShowViewPlane(bool toShow)
+{
+  PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  if (toShow) {
+    CompositeFeaturePtr aSketch = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(myFeature);
+    aModule->sketchMgr()->previewSketchPlane()->createSketchPlane(aSketch, myWorkshop);
+  }
+  else {
+    aModule->sketchMgr()->previewSketchPlane()->eraseSketchPlane(myWorkshop, false);
+  }
+  myWorkshop->viewer()->update();
+}
index e6eec1e01bedc23bd524aad3ed25b06d792948e7..bad4d67cdc55749488492c35da75489d96efda90 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <ModuleBase_WidgetValidated.h>
 #include <ModuleBase_ViewerFilters.h>
+#include <ModuleBase_ViewerPrs.h>
 
 #include <GeomAPI_Dir.h>
 
@@ -110,7 +111,7 @@ public:
 
   /// Returns True if the selected presentation can be used for plane definition
   /// \param thePrs a presentation
-  static bool canFillSketch(const std::shared_ptr<ModuleBase_ViewerPrs>& thePrs);
+  static bool canFillSketch(const ModuleBase_ViewerPrsPtr& thePrs);
 
   /// If widgets has several panels then this method has to show a page which contains information
   /// for current feature. By default does nothing
@@ -152,7 +153,7 @@ protected:
 
   /// Fills the attribute with the value of the selected owner
   /// \param thePrs a selected owner
-  virtual bool setSelectionCustom(const std::shared_ptr<ModuleBase_ViewerPrs>& thePrs);
+  virtual bool setSelectionCustom(const ModuleBase_ViewerPrsPtr& thePrs);
 
   /// Saves the internal parameters to the given feature
   /// \return True in success
@@ -190,17 +191,17 @@ protected:
   /// It is redefined to do nothing if the plane of the sketch has been already set.
   /// \param theValues the wrapped selection values
   /// \param theToValidate a validation flag
-  bool setSelectionInternal(const QList<std::shared_ptr<ModuleBase_ViewerPrs>>& theValues,
+  bool setSelectionInternal(const QList<ModuleBase_ViewerPrsPtr>& theValues,
                             const bool theToValidate);
 
   /// Erase preview planes, disconnect widget, change the view projection
   /// \param thePrs a selected presentation
-  void updateByPlaneSelected(const std::shared_ptr<ModuleBase_ViewerPrs>& thePrs);
+  void updateByPlaneSelected(const ModuleBase_ViewerPrsPtr& thePrs);
 
   /// Set sketch plane from selected object
   /// \param theFeature a feature of sketch
   /// \param thePrs a presentation
-  bool fillSketchPlaneBySelection(const std::shared_ptr<ModuleBase_ViewerPrs>& thePrs);
+  bool fillSketchPlaneBySelection(const ModuleBase_ViewerPrsPtr& thePrs);
 
   /// Redefinition of a virtual function
   virtual void showEvent(QShowEvent* theEvent);
@@ -219,12 +220,18 @@ private slots:
   /// \param theOn a flag show constraints or not
   void onShowConstraint(bool theOn);
 
+  /// A a slot called on "Change sketch plane" check box toggele
   void onChangePlane();
 
+  /// A a slot called on "Show remaining DOFs" check box toggele
   void onShowDOF();
 
+  ///  A a slot called on changing the panel visibility
   void onShowPanel();
 
+  /// A slot which is called on "Visible" plane checkbox toggle
+  void onShowViewPlane(bool);
+
 private:
   /// Set sketch plane by shape
   /// \param theShape a planar face
@@ -239,13 +246,14 @@ private:
   * (circles, arcs) which are in pane of of the given sketch
   * \param theSketch - the sketch
   */
-  QList<std::shared_ptr<ModuleBase_ViewerPrs>> findCircularEdgesInPlane();
+  QList<ModuleBase_ViewerPrsPtr> findCircularEdgesInPlane();
 
 private:
   /// class to show/hide preview planes
   PartSet_PreviewPlanes* myPreviewPlanes;
 
   QCheckBox* myViewInverted;
+  QCheckBox* myViewVisible;
   QCheckBox* myRemoveExternal;
   QCheckBox* myShowPoints;
   QCheckBox* myAutoConstraints;
index d41af2b70030588c43c3a67b9387bab98b4a6177..8d688054b9ebfe260b69b415708f2916e1069fbc 100644 (file)
@@ -58,20 +58,28 @@ class DumpAssistant(ModelHighAPI.ModelHighAPI_Dumper):
 
     ## Create wrapper for a given feature and dump it
     def dumpFeature(self, theFeature, theForce):
+        aDumper = None
         aFeatureKind = theFeature.getKind()
         if aFeatureKind in self.myFeatures:
             # Dump only feature created by user (in history).
             # Also dump Export and RemoveResults features (hard-coded here in order not to change the data model).
             # For all other features, just keep their name.
             if theForce or theFeature.isInHistory() or aFeatureKind=="Export" or aFeatureKind=="RemoveResults":
-                self.myFeatures[aFeatureKind](theFeature).dump(self)
+                aDumper = self.myFeatures[aFeatureKind](theFeature)
+                # Dump comment for the operation before the dumping of the feature to improve the readability of a script.
+                if self.dumpCommentBeforeFeature(theFeature):
+                    self.__print__("\n### Create " + theFeature.getKind())
+                    self.newline()
             else:
                 self.name(theFeature)
                 self.clearNotDumped()
         else:
             # Probably the feature is a constraint, try to dump it with SketchAPI_Constraint.
             # In case of theFeature is not a constraint, it will not be dumped.
-            self.myFeatures[SketchAPI.SketchAPI_Constraint.ID()](theFeature).dump(self)
+            self.name(theFeature, False, True, True)
+            aDumper = self.myFeatures[SketchAPI.SketchAPI_Constraint.ID()](theFeature)
+        if aDumper is not None:
+            aDumper.dump(self)
 
     ## Create wrapper for a folder and dump it
     def dumpFolder(self, theFolder):
index 617498e0fa27982f6071f1c00f6364bff0e13e75..7934de9f288fb160c5ffe2f3f7eaf83dbbe1b2fa 100644 (file)
@@ -35,6 +35,7 @@ SET(PROJECT_HEADERS
   SketchAPI_MacroEllipse.h
   SketchAPI_MacroEllipticArc.h
   SketchAPI_Mirror.h
+  SketchAPI_Offset.h
   SketchAPI_Point.h
   SketchAPI_Projection.h
   SketchAPI_Rectangle.h
@@ -59,6 +60,7 @@ SET(PROJECT_SOURCES
   SketchAPI_MacroEllipse.cpp
   SketchAPI_MacroEllipticArc.cpp
   SketchAPI_Mirror.cpp
+  SketchAPI_Offset.cpp
   SketchAPI_Point.cpp
   SketchAPI_Projection.cpp
   SketchAPI_Rectangle.cpp
index 03bb90f3ec7e6837aa4282752cc499a18f67d7a1..66b07c3cc3603105f2a6d4ef40fdaa1670f17914 100644 (file)
@@ -50,6 +50,9 @@
 %feature("kwargs") SketchAPI_BSpline::controlPolygon;
 %feature("kwargs") SketchAPI_Ellipse::construction;
 %feature("kwargs") SketchAPI_EllipticArc::construction;
+%feature("kwargs") SketchAPI_Sketch::addApproximation;
+%feature("kwargs") SketchAPI_Sketch::addInterpolation;
+%feature("kwargs") SketchAPI_Sketch::addProjection;
 %feature("kwargs") SketchAPI_Sketch::addSpline;
 %feature("kwargs") SketchAPI_Sketch::setAngle;
 
@@ -69,6 +72,7 @@
 %shared_ptr(SketchAPI_IntersectionPoint)
 %shared_ptr(SketchAPI_Line)
 %shared_ptr(SketchAPI_Mirror)
+%shared_ptr(SketchAPI_Offset)
 %shared_ptr(SketchAPI_Sketch)
 %shared_ptr(SketchAPI_SketchEntity)
 %shared_ptr(SketchAPI_Point)
 %include "SketchAPI_BSpline.h"
 %include "SketchAPI_Projection.h"
 %include "SketchAPI_Mirror.h"
+%include "SketchAPI_Offset.h"
 %include "SketchAPI_Translation.h"
 %include "SketchAPI_Rectangle.h"
 %include "SketchAPI_Rotation.h"
index b09ec6de740ebed346e8f26cef99921d7572c7c5..9b0fb1827cdc663caf1a6422fdabf11942bf3b99 100644 (file)
@@ -194,7 +194,7 @@ void SketchAPI_Constraint::dump(ModelHighAPI_Dumper& theDumper) const
     return;
 
   const std::string& aSketchName = theDumper.parentName(aBase);
-  theDumper << aBase << " = " << aSketchName << "." << aSetter << "(";
+  theDumper << aSketchName << "." << aSetter << "(";
 
   bool isFirstAttr = true;
   for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
index 038ee0c15a8ac08cf174adf1998877d11f12f7c9..c9abfa460507829f66264b521efddac8b96b523d 100644 (file)
@@ -129,7 +129,8 @@ void SketchAPI_ConstraintAngle::dump(ModelHighAPI_Dumper& theDumper) const
   FeaturePtr aBase = feature();
 
   const std::string& aSketchName = theDumper.parentName(aBase);
-  theDumper << aBase << " = " << aSketchName << "." << "setAngle(";
+  theDumper.name(aBase, false, true, true); // mark constraint as dumped
+  theDumper << aSketchName << "." << "setAngle(";
 
   bool isFirstAttr = true;
   for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
diff --git a/src/SketchAPI/SketchAPI_Offset.cpp b/src/SketchAPI/SketchAPI_Offset.cpp
new file mode 100644 (file)
index 0000000..392d07f
--- /dev/null
@@ -0,0 +1,105 @@
+// Copyright (C) 2014-2019  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 "SketchAPI_Offset.h"
+#include <SketchAPI_SketchEntity.h>
+//--------------------------------------------------------------------------------------
+#include <ModelHighAPI_Dumper.h>
+#include <ModelHighAPI_Selection.h>
+#include <ModelHighAPI_Tools.h>
+
+//--------------------------------------------------------------------------------------
+SketchAPI_Offset::SketchAPI_Offset (const std::shared_ptr<ModelAPI_Feature> & theFeature)
+  : ModelHighAPI_Interface(theFeature)
+{
+  initialize();
+}
+
+SketchAPI_Offset::SketchAPI_Offset (const std::shared_ptr<ModelAPI_Feature> & theFeature,
+                                    const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
+                                    const ModelHighAPI_Double & theOffsetValue,
+                                    bool theIsReversed)
+  : ModelHighAPI_Interface(theFeature)
+{
+  if (initialize()) {
+    fillAttribute(theObjects, edgesList());
+    fillAttribute(theOffsetValue, value());
+    fillAttribute(theIsReversed, reversed());
+
+    execute();
+  }
+}
+
+SketchAPI_Offset::~SketchAPI_Offset()
+{
+}
+
+std::list<std::shared_ptr<SketchAPI_SketchEntity> > SketchAPI_Offset::offset() const
+{
+  std::list<ObjectPtr> aList = feature()->reflist(SketchPlugin_Constraint::ENTITY_B())->list();
+  std::list<FeaturePtr> anIntermediate;
+  std::list<ObjectPtr>::const_iterator anIt = aList.begin();
+  for (; anIt != aList.end(); ++anIt) {
+    FeaturePtr aFeature = ModelAPI_Feature::feature(*anIt);
+    anIntermediate.push_back(aFeature);
+  }
+  return SketchAPI_SketchEntity::wrap(anIntermediate);
+}
+
+//--------------------------------------------------------------------------------------
+
+void SketchAPI_Offset::dump (ModelHighAPI_Dumper& theDumper) const
+{
+  FeaturePtr aBase = feature();
+  const std::string& aSketchName = theDumper.parentName(aBase);
+
+  AttributeRefListPtr aOffsetObjects = edgesList();
+  AttributeDoublePtr aValue = value();
+  AttributeBooleanPtr aReversed = reversed();
+
+  // Check all attributes are already dumped. If not, store the feature as postponed.
+  if (!theDumper.isDumped(aOffsetObjects)) {
+    theDumper.postpone(aBase);
+    return;
+  }
+
+  theDumper << aBase << " = " << aSketchName << ".addOffset(" << aOffsetObjects << ", "
+            << aValue << ", " << aReversed << ")" << std::endl;
+
+  // Dump variables for a list of created features
+  theDumper << "[";
+  std::list<std::shared_ptr<SketchAPI_SketchEntity> > aList = offset();
+  std::list<std::shared_ptr<SketchAPI_SketchEntity> >::const_iterator anIt = aList.begin();
+  for (; anIt != aList.end(); ++anIt) {
+    if (anIt != aList.begin())
+      theDumper << ", ";
+    theDumper << (*anIt)->feature();
+  }
+  theDumper << "] = " << theDumper.name(aBase) << ".offset()" << std::endl;
+
+////  // Set necessary "auxiliary" flag for created features
+////  // (flag is set if it differs to anAux)
+////  for (anIt = aList.begin(); anIt != aList.end(); ++anIt) {
+////    FeaturePtr aFeature = (*anIt)->feature();
+////    bool aFeatAux = aFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value();
+////    if (aFeatAux != anAux->value())
+////      theDumper << theDumper.name((*anIt)->feature(), false)
+////                << ".setAuxiliary(" << aFeatAux << ")" <<std::endl;
+////  }
+}
diff --git a/src/SketchAPI/SketchAPI_Offset.h b/src/SketchAPI/SketchAPI_Offset.h
new file mode 100644 (file)
index 0000000..b4d9ca9
--- /dev/null
@@ -0,0 +1,83 @@
+// Copyright (C) 2014-2019  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 SRC_SKETCHAPI_SKETCHAPI_OFFSET_H_
+#define SRC_SKETCHAPI_SKETCHAPI_OFFSET_H_
+
+//--------------------------------------------------------------------------------------
+#include "SketchAPI.h"
+#include "SketchAPI_SketchEntity.h"
+
+#include <list>
+
+#include <SketchPlugin_Offset.h>
+
+#include <ModelHighAPI_Interface.h>
+#include <ModelHighAPI_Macro.h>
+//--------------------------------------------------------------------------------------
+class ModelAPI_Object;
+class ModelHighAPI_RefAttr;
+class ModelHighAPI_Double;
+//--------------------------------------------------------------------------------------
+/**\class SketchAPI_Offset
+ * \ingroup CPPHighAPI
+ * \brief Interface for Offset feature
+ */
+class SketchAPI_Offset : public ModelHighAPI_Interface
+{
+public:
+  /// Constructor without values
+  SKETCHAPI_EXPORT
+  explicit SketchAPI_Offset(const std::shared_ptr<ModelAPI_Feature> & theFeature);
+  /// Constructor with values
+  SKETCHAPI_EXPORT
+  SketchAPI_Offset(const std::shared_ptr<ModelAPI_Feature> & theFeature,
+                   const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
+                   const ModelHighAPI_Double & theOffsetValue,
+                   bool theIsReversed);
+  /// Destructor
+  SKETCHAPI_EXPORT
+  virtual ~SketchAPI_Offset();
+
+  INTERFACE_3(SketchPlugin_Offset::ID(),
+
+              edgesList, SketchPlugin_Offset::EDGES_ID(),
+              ModelAPI_AttributeRefList, /** Offset edges list */,
+
+              value, SketchPlugin_Offset::VALUE_ID(),
+              ModelAPI_AttributeDouble, /** Value */,
+
+              reversed, SketchPlugin_Offset::REVERSED_ID(),
+              ModelAPI_AttributeBoolean, /** Negative value */
+              )
+
+  /// List of created objects
+  SKETCHAPI_EXPORT
+  std::list<std::shared_ptr<SketchAPI_SketchEntity> > offset() const;
+
+  /// Dump wrapped feature
+  virtual void dump(ModelHighAPI_Dumper& theDumper) const;
+};
+
+//! Pointer on Offset object
+typedef std::shared_ptr<SketchAPI_Offset> OffsetPtr;
+
+//--------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------
+#endif /* SRC_SKETCHAPI_SKETCHAPI_OFFSET_H_ */
index 225188378008f850cb73ef2af2e6e1944ff12613..bee330063ecb5c3658fe1746e655e3d247057881 100644 (file)
@@ -56,16 +56,6 @@ SketchAPI_Projection::SketchAPI_Projection(
   }
 }
 
-SketchAPI_Projection::SketchAPI_Projection(
-    const std::shared_ptr<ModelAPI_Feature> & theFeature,
-    const std::string & theExternalName)
-: SketchAPI_SketchEntity(theFeature)
-{
-  if (initialize()) {
-    setByExternalName(theExternalName);
-  }
-}
-
 SketchAPI_Projection::~SketchAPI_Projection()
 {
 
@@ -79,14 +69,18 @@ void SketchAPI_Projection::setExternalFeature(const ModelHighAPI_Selection & the
   execute(true);
 }
 
-void SketchAPI_Projection::setByExternalName(const std::string& theExternalName)
+void SketchAPI_Projection::setIncludeToResult(bool theKeepResult)
 {
-  setExternalFeature(ModelHighAPI_Selection("EDGE", theExternalName));
+  fillAttribute(theKeepResult, includeToResult());
+  execute(true);
 }
 
-void SketchAPI_Projection::setIncludeToResult(bool theKeepResult)
+void SketchAPI_Projection::setKeepReferenceToOriginal(bool theKeepRefToOriginal)
 {
-  fillAttribute(theKeepResult, includeToResult());
+  // the Fixed constraint should be assigned explicitly
+  fillAttribute(false, feature()->boolean(SketchPlugin_Projection::MAKE_FIXED()));
+  fillAttribute(theKeepRefToOriginal ? "true" : "false",
+                feature()->string(SketchPlugin_Projection::KEEP_REFERENCE_ID()));
   execute(true);
 }
 
index d064f170d77e4b0f229c3a35222a4d7602a746f0..cfa3af1ac21e24fb51d59b3ee324729cb628518d 100644 (file)
@@ -43,10 +43,6 @@ public:
   SKETCHAPI_EXPORT
   SketchAPI_Projection(const std::shared_ptr<ModelAPI_Feature> & theFeature,
                        const ModelHighAPI_Selection & theExternalFeature);
-  /// Constructor with values
-  SKETCHAPI_EXPORT
-  SketchAPI_Projection(const std::shared_ptr<ModelAPI_Feature> & theFeature,
-                       const std::string & theExternalName);
   /// Destructor
   SKETCHAPI_EXPORT
   virtual ~SketchAPI_Projection();
@@ -66,14 +62,14 @@ public:
   SKETCHAPI_EXPORT
   void setExternalFeature(const ModelHighAPI_Selection & theExternalLine);
 
-  /// Set by external name
-  SKETCHAPI_EXPORT
-  void setByExternalName(const std::string & theExternalName);
-
   /// Set flag to include projection to result or not
   SKETCHAPI_EXPORT
   void setIncludeToResult(bool theKeepResult);
 
+  /// Set flag to keep the reference to the original shape
+  SKETCHAPI_EXPORT
+  void setKeepReferenceToOriginal(bool theKeepRefToOriginal);
+
   /// Returns created feature
   SKETCHAPI_EXPORT
   std::shared_ptr<SketchAPI_SketchEntity> createdFeature() const;
index 9bdecc85f2d860c1dcc78d0176039527f52d0c43..e76cfb3a21199146d85031975feb9fd6e33a557b 100644 (file)
 #include <SketchPlugin_ConstraintPerpendicular.h>
 #include <SketchPlugin_ConstraintRadius.h>
 #include <SketchPlugin_ConstraintRigid.h>
+#include <SketchPlugin_CurveFitting.h>
 #include <SketchPlugin_Trim.h>
 #include <SketchPlugin_Split.h>
 #include <SketchPlugin_ConstraintTangent.h>
 #include <SketchPlugin_ConstraintVertical.h>
 #include <SketchPlugin_MacroBSpline.h>
 #include <SketchPlugin_SketchCopy.h>
+#include <SketchPlugin_Offset.h>
 #include <SketcherPrs_Tools.h>
 //--------------------------------------------------------------------------------------
 #include <ModelAPI_Events.h>
@@ -66,6 +68,7 @@
 #include "SketchAPI_MacroEllipse.h"
 #include "SketchAPI_MacroEllipticArc.h"
 #include "SketchAPI_Mirror.h"
+#include "SketchAPI_Offset.h"
 #include "SketchAPI_Point.h"
 #include "SketchAPI_Projection.h"
 #include "SketchAPI_Rectangle.h"
@@ -794,25 +797,79 @@ std::shared_ptr<SketchAPI_BSpline> SketchAPI_Sketch::addSpline(
 }
 
 //--------------------------------------------------------------------------------------
-std::shared_ptr<SketchAPI_Projection> SketchAPI_Sketch::addProjection(
-    const ModelHighAPI_Selection & theExternalFeature,
-    bool theKeepResult)
+static std::shared_ptr<SketchAPI_BSpline> buildInterpolation(
+    const CompositeFeaturePtr& theSketch,
+    const FeaturePtr& theCurveFittingFeature,
+    const std::list<ModelHighAPI_RefAttr>& points,
+    const bool periodic,
+    const bool closed)
+{
+  AttributeBooleanPtr aPeriodicAttr =
+      theCurveFittingFeature->boolean(SketchPlugin_CurveFitting::PERIODIC_ID());
+  fillAttribute(periodic, aPeriodicAttr);
+  AttributeBooleanPtr aClosedAttr =
+      theCurveFittingFeature->boolean(SketchPlugin_CurveFitting::CLOSED_ID());
+  fillAttribute(closed, aClosedAttr);
+  AttributeRefAttrListPtr aPointsAttr =
+      theCurveFittingFeature->refattrlist(SketchPlugin_CurveFitting::POINTS_ID());
+  fillAttribute(points, aPointsAttr);
+  apply(); // to execute and kill the macro-feature
+
+  // find created B-spline feature
+  BSplinePtr aBSpline;
+  const std::string& aKindToFind =
+      periodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID();
+  int aNbSubs = theSketch->numberOfSubs();
+  for (int anIndex = aNbSubs - 1; anIndex >= 0; --anIndex) {
+    FeaturePtr aFeature = theSketch->subFeature(anIndex);
+    if (aFeature->getKind() == aKindToFind) {
+      aBSpline.reset(periodic ? new SketchAPI_BSplinePeriodic(aFeature)
+        : new SketchAPI_BSpline(aFeature));
+      aBSpline->execute();
+      break;
+    }
+  }
+  return aBSpline;
+}
+
+std::shared_ptr<SketchAPI_BSpline> SketchAPI_Sketch::addInterpolation(
+    const std::list<ModelHighAPI_RefAttr>& points,
+    const bool periodic,
+    const bool closed)
 {
-  std::shared_ptr<ModelAPI_Feature> aFeature =
-    compositeFeature()->addFeature(SketchPlugin_Projection::ID());
-  ProjectionPtr aProjection(new SketchAPI_Projection(aFeature, theExternalFeature));
-  aProjection->setIncludeToResult(theKeepResult);
-  return aProjection;
+  CompositeFeaturePtr aSketch = compositeFeature();
+  FeaturePtr anInterpFeature = aSketch->addFeature(SketchPlugin_CurveFitting::ID());
+  anInterpFeature->string(SketchPlugin_CurveFitting::TYPE_ID())
+      ->setValue(SketchPlugin_CurveFitting::TYPE_INTERPOLATION_ID());
+  return buildInterpolation(aSketch, anInterpFeature, points, periodic, closed);
+}
+
+std::shared_ptr<SketchAPI_BSpline> SketchAPI_Sketch::addApproximation(
+    const std::list<ModelHighAPI_RefAttr>& points,
+    const ModelHighAPI_Double& precision,
+    const bool periodic,
+    const bool closed)
+{
+  CompositeFeaturePtr aSketch = compositeFeature();
+  FeaturePtr anInterpFeature = aSketch->addFeature(SketchPlugin_CurveFitting::ID());
+  anInterpFeature->string(SketchPlugin_CurveFitting::TYPE_ID())
+      ->setValue(SketchPlugin_CurveFitting::TYPE_APPROXIMATION_ID());
+  fillAttribute(precision, anInterpFeature->real(SketchPlugin_CurveFitting::PRECISION_ID()));
+  return buildInterpolation(aSketch, anInterpFeature, points, periodic, closed);
 }
 
+//--------------------------------------------------------------------------------------
 std::shared_ptr<SketchAPI_Projection> SketchAPI_Sketch::addProjection(
-    const std::string & theExternalName,
-    bool theKeepResult)
+    const ModelHighAPI_Selection & theExternalFeature,
+    bool keepResult,
+    bool keepRefToOriginal)
 {
   std::shared_ptr<ModelAPI_Feature> aFeature =
     compositeFeature()->addFeature(SketchPlugin_Projection::ID());
-  ProjectionPtr aProjection(new SketchAPI_Projection(aFeature, theExternalName));
-  aProjection->setIncludeToResult(theKeepResult);
+  ProjectionPtr aProjection(new SketchAPI_Projection(aFeature));
+  aProjection->setIncludeToResult(keepResult);
+  aProjection->setKeepReferenceToOriginal(keepRefToOriginal);
+  aProjection->setExternalFeature(theExternalFeature);
   return aProjection;
 }
 
@@ -826,6 +883,17 @@ std::shared_ptr<SketchAPI_Mirror> SketchAPI_Sketch::addMirror(
   return MirrorPtr(new SketchAPI_Mirror(aFeature, theMirrorLine, theObjects));
 }
 
+//--------------------------------------------------------------------------------------
+std::shared_ptr<SketchAPI_Offset> SketchAPI_Sketch::addOffset(
+    const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
+    const ModelHighAPI_Double & theValue,
+    const bool theReversed)
+{
+  std::shared_ptr<ModelAPI_Feature> aFeature =
+    compositeFeature()->addFeature(SketchPlugin_Offset::ID());
+  return OffsetPtr(new SketchAPI_Offset(aFeature, theObjects, theValue, theReversed));
+}
+
 //--------------------------------------------------------------------------------------
 std::shared_ptr<SketchAPI_Translation> SketchAPI_Sketch::addTranslation(
     const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
index 319884404b8ceccbde704104bff6b76590ad0bc1..2bdf2a570d45c22facd60346bc1896a0583d866f 100644 (file)
 #include <SketchPlugin_Sketch.h>
 #include <SketchPlugin_SketchEntity.h>
 
+#include <ModelHighAPI_Double.h>
 #include <ModelHighAPI_Interface.h>
 #include <ModelHighAPI_Macro.h>
 #include <ModelHighAPI_Selection.h>
 //--------------------------------------------------------------------------------------
 class ModelAPI_CompositeFeature;
 class ModelAPI_Object;
-class ModelHighAPI_Double;
 class ModelHighAPI_Integer;
 class ModelHighAPI_RefAttr;
 class ModelHighAPI_Reference;
@@ -50,6 +50,7 @@ class SketchAPI_BSpline;
 class SketchAPI_IntersectionPoint;
 class SketchAPI_Line;
 class SketchAPI_Mirror;
+class SketchAPI_Offset;
 class SketchAPI_Point;
 class SketchAPI_Projection;
 class SketchAPI_Rectangle;
@@ -338,16 +339,27 @@ public:
       const std::list<ModelHighAPI_Integer>& multiplicities = std::list<ModelHighAPI_Integer>(),
       const bool periodic = false);
 
-  /// Add projection
+  /// Add interpolation feature
   SKETCHAPI_EXPORT
-  std::shared_ptr<SketchAPI_Projection> addProjection(
-      const ModelHighAPI_Selection & theExternalFeature,
-      bool theKeepResult = false);
+  std::shared_ptr<SketchAPI_BSpline> addInterpolation(
+      const std::list<ModelHighAPI_RefAttr>& points,
+      const bool periodic = false,
+      const bool closed = false);
+
+  /// Add approximation feature
+  SKETCHAPI_EXPORT
+  std::shared_ptr<SketchAPI_BSpline> addApproximation(
+      const std::list<ModelHighAPI_RefAttr>& points,
+      const ModelHighAPI_Double& precision = ModelHighAPI_Double(1.e-3),
+      const bool periodic = false,
+      const bool closed = false);
 
   /// Add projection
   SKETCHAPI_EXPORT
-  std::shared_ptr<SketchAPI_Projection> addProjection(const std::string & theExternalName,
-                                                      bool theKeepResult = false);
+  std::shared_ptr<SketchAPI_Projection> addProjection(
+      const ModelHighAPI_Selection & theExternalFeature,
+      bool keepResult = false,
+      bool keepRefToOriginal = true);
 
   /// Add mirror
   SKETCHAPI_EXPORT
@@ -355,6 +367,13 @@ public:
       const ModelHighAPI_RefAttr & theMirrorLine,
       const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects);
 
+  /// Add offset
+  SKETCHAPI_EXPORT
+  std::shared_ptr<SketchAPI_Offset> addOffset(
+      const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
+      const ModelHighAPI_Double & theValue,
+      const bool theReversed);
+
   /// Add translation
   SKETCHAPI_EXPORT
   std::shared_ptr<SketchAPI_Translation> addTranslation(
index 43422dd3c3f97c008dda46d286f2f43d1b351ddf..e43232dd3b6acaae6b9fa9de0c81ec917fb72152 100644 (file)
@@ -37,6 +37,7 @@
   #include "SketchAPI_IntersectionPoint.h"
   #include "SketchAPI_Line.h"
   #include "SketchAPI_Mirror.h"
+  #include "SketchAPI_Offset.h"
   #include "SketchAPI_Sketch.h"
   #include "SketchAPI_SketchEntity.h"
   #include "SketchAPI_Point.h"
index b12543d8b1cf68e3673c2f2ba49ad1988d3ee724..ea656e9398a04ddd62a162e89b3109b1a1e4232b 100644 (file)
@@ -173,7 +173,7 @@ class SketchTestCase(unittest.TestCase):
     def test_arc_by_projection(self):
         """ Test 10. Create arc by projection of external feature
         """
-        self.sketch.addProjection("[Cylinder_2_1/Face_1][Cylinder_2_1/Face_3]")
+        self.sketch.addProjection(model.selection("EDGE", "[Cylinder_2_1/Face_1][Cylinder_2_1/Face_3]"))
         model.do()
         anArc = SketchAPI.SketchAPI_Arc(model.lastSubFeature(self.sketch, "SketchArc"))
 
index 01aa67fd842c4a2c85ae14dc911e57a4e12ad30c..13e6ae0fad1acd1c2360da5daa56a72808f1fa84 100644 (file)
@@ -49,6 +49,7 @@ SET(PROJECT_HEADERS
     SketchPlugin_ConstraintRigid.h
     SketchPlugin_ConstraintTangent.h
     SketchPlugin_ConstraintVertical.h
+    SketchPlugin_CurveFitting.h
     SketchPlugin_Ellipse.h
     SketchPlugin_EllipticArc.h
     SketchPlugin_ExternalValidator.h
@@ -63,6 +64,7 @@ SET(PROJECT_HEADERS
     SketchPlugin_MacroEllipticArc.h
     SketchPlugin_MultiRotation.h
     SketchPlugin_MultiTranslation.h
+    SketchPlugin_Offset.h
     SketchPlugin_Plugin.h
     SketchPlugin_Point.h
     SketchPlugin_Projection.h
@@ -103,6 +105,7 @@ SET(PROJECT_SOURCES
     SketchPlugin_ConstraintRigid.cpp
     SketchPlugin_ConstraintTangent.cpp
     SketchPlugin_ConstraintVertical.cpp
+    SketchPlugin_CurveFitting.cpp
     SketchPlugin_Ellipse.cpp
     SketchPlugin_EllipticArc.cpp
     SketchPlugin_ExternalValidator.cpp
@@ -116,6 +119,7 @@ SET(PROJECT_SOURCES
     SketchPlugin_MacroEllipticArc.cpp
     SketchPlugin_MultiRotation.cpp
     SketchPlugin_MultiTranslation.cpp
+    SketchPlugin_Offset.cpp
     SketchPlugin_Plugin.cpp
     SketchPlugin_Point.cpp
     SketchPlugin_Projection.cpp
@@ -294,6 +298,10 @@ ADD_UNIT_TESTS(
   TestCreateEllipticArc.py
   TestCreateEllipticArcByExternal.py
   TestCreateMacroBSpline.py
+  TestCurveFitting1.py
+  TestCurveFitting2.py
+  TestCurveFitting3.py
+  TestCurveFitting4.py
   TestDegeneratedGeometry.py
   TestDistanceDump.py
   TestDistanceSignedVsUnsigned01.py
@@ -319,6 +327,8 @@ ADD_UNIT_TESTS(
   TestMultiRotation05.py
   TestMultiRotationWithParameter.py
   TestMultiTranslation.py
+  TestOffset1.py
+  TestOffset2.py
   TestPresentation.py
   TestProjection.py
   TestProjectionBSpline.py
@@ -327,6 +337,7 @@ ADD_UNIT_TESTS(
   TestProjectionEllipticArc.py
   TestProjectionIntoResult.py
   TestProjectionUpdate.py
+  TestProjectionWithoutReference.py
   TestRectangle.py
   TestRemainingDoF.py
   TestRemoveBSpline.py
diff --git a/src/SketchPlugin/SketchPlugin_CurveFitting.cpp b/src/SketchPlugin/SketchPlugin_CurveFitting.cpp
new file mode 100644 (file)
index 0000000..f775679
--- /dev/null
@@ -0,0 +1,386 @@
+// 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 <SketchPlugin_CurveFitting.h>
+
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
+#include <SketchPlugin_ConstraintCoincidence.h>
+#include <SketchPlugin_MacroBSpline.h>
+#include <SketchPlugin_Point.h>
+#include <SketchPlugin_Tools.h>
+#include <SketchPlugin_Sketch.h>
+
+#include <ModelAPI_AttributeBoolean.h>
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_AttributeRefAttrList.h>
+#include <ModelAPI_AttributeString.h>
+#include <ModelAPI_Session.h>
+#include <ModelAPI_Validator.h>
+
+#include <Events_InfoMessage.h>
+
+#include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <GeomAlgoAPI_CurveBuilder.h>
+#include <GeomAlgoAPI_EdgeBuilder.h>
+
+#include <GeomAPI_BSpline.h>
+
+static void convertTo3D(SketchPlugin_Sketch* theSketch,
+                        const AttributeRefAttrListPtr& theAttribute,
+                        bool theClosedButNotPeriodic,
+                        std::list<GeomPointPtr>& thePoints);
+
+static GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch,
+                                           AttributeRefAttrListPtr thePoints,
+                                           bool thePeriodic,
+                                           bool theClosed);
+static GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch,
+                                           AttributeRefAttrListPtr thePoints,
+                                           double thePrecision,
+                                           bool thePeriodic,
+                                           bool theClosed);
+
+
+SketchPlugin_CurveFitting::SketchPlugin_CurveFitting()
+  : SketchPlugin_SketchEntity()
+{
+}
+
+void SketchPlugin_CurveFitting::initDerivedClassAttributes()
+{
+  data()->addAttribute(POINTS_ID(), ModelAPI_AttributeRefAttrList::typeId());
+
+  data()->addAttribute(TYPE_ID(), ModelAPI_AttributeString::typeId());
+
+  data()->addAttribute(PRECISION_ID(), ModelAPI_AttributeDouble::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), PRECISION_ID());
+
+  data()->addAttribute(NEED_CONTROL_POLYGON_ID(), ModelAPI_AttributeBoolean::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(),
+                                                               NEED_CONTROL_POLYGON_ID());
+
+  data()->addAttribute(PERIODIC_ID(), ModelAPI_AttributeBoolean::typeId());
+  data()->addAttribute(CLOSED_ID(), ModelAPI_AttributeBoolean::typeId());
+}
+
+void SketchPlugin_CurveFitting::execute()
+{
+  FeaturePtr aBSpline = createBSplineFeature();
+  // create control polygon
+  AttributeBooleanPtr aNeedControlPoly = boolean(NEED_CONTROL_POLYGON_ID());
+  if (aNeedControlPoly && aNeedControlPoly->isInitialized() && aNeedControlPoly->value()) {
+    bool isPeriodic = boolean(PERIODIC_ID())->value();
+    std::list<FeaturePtr> aControlPoles;
+    SketchPlugin_MacroBSpline::createControlPolygon(aBSpline, isPeriodic, aControlPoles);
+  }
+  // constraints for the selected points
+  createConstraints(aBSpline);
+}
+
+FeaturePtr SketchPlugin_CurveFitting::createBSplineFeature()
+{
+  // create transient curve if not created yet
+  if (!myTransientResult) {
+    getAISObject(AISObjectPtr());
+    if (!myTransientResult)
+      return FeaturePtr();
+  }
+
+  SketchPlugin_Sketch* aSketch = sketch();
+  if (!aSketch)
+    return FeaturePtr();
+
+  bool isPeriodic = boolean(PERIODIC_ID())->value();
+
+  FeaturePtr aBSplineFeature = aSketch->addFeature(
+      isPeriodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID());
+
+  // Convert transient edge to B-spline curve.
+  GeomCurvePtr aCurve = std::make_shared<GeomAPI_Curve>(myTransientResult);
+  std::shared_ptr<GeomAPI_BSpline> aBSpline = std::make_shared<GeomAPI_BSpline>(aCurve);
+
+  // Fill attributes of B-spline feature:
+  bool aWasBlocked = aBSplineFeature->data()->blockSendAttributeUpdated(true, false);
+  // 1. Degree
+  aBSplineFeature->integer(SketchPlugin_BSplineBase::DEGREE_ID())->setValue(aBSpline->degree());
+  // 2. Poles
+  std::list<GeomPointPtr> aPoles = aBSpline->poles();
+  AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      aBSplineFeature->attribute(SketchPlugin_BSplineBase::POLES_ID()));
+  aPolesAttr->setSize((int)aPoles.size());
+  int anIndex = 0;
+  for (auto it = aPoles.begin(); it != aPoles.end(); ++it, ++anIndex)
+    aPolesAttr->setPnt(anIndex, aSketch->to2D(*it));
+  // 3. Weights
+  std::list<double> aWeights = aBSpline->weights();
+  AttributeDoubleArrayPtr aWeightsAttr =
+      aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::WEIGHTS_ID());
+  if (aWeights.empty()) {
+    aWeightsAttr->setSize((int)aPoles.size());
+    for (anIndex = 0; anIndex < (int)aPoles.size(); ++anIndex)
+      aWeightsAttr->setValue(anIndex, 1.0);
+  }
+  else {
+    aWeightsAttr->setSize((int)aWeights.size());
+    anIndex = 0;
+    for (auto it = aWeights.begin(); it != aWeights.end(); ++it, ++anIndex)
+      aWeightsAttr->setValue(anIndex, *it);
+  }
+  // 4. Knots (normalized from 0 to 1)
+  std::list<double> aKnots = aBSpline->knots();
+  AttributeDoubleArrayPtr aKnotsAttr =
+      aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::KNOTS_ID());
+  aKnotsAttr->setSize((int)aKnots.size());
+  double aFirstKnot = aKnots.front();
+  double aLastKnot = aKnots.back();
+  anIndex = 0;
+  for (auto it = aKnots.begin(); it != aKnots.end(); ++it, ++anIndex)
+    aKnotsAttr->setValue(anIndex, (*it - aFirstKnot) / (aLastKnot - aFirstKnot));
+  // 5. Multiplicities
+  std::list<int> aMults = aBSpline->mults();
+  AttributeIntArrayPtr aMultsAttr =
+      aBSplineFeature->data()->intArray(SketchPlugin_BSplineBase::MULTS_ID());
+  aMultsAttr->setSize((int)aMults.size());
+  anIndex = 0;
+  for (auto it = aMults.begin(); it != aMults.end(); ++it, ++anIndex)
+    aMultsAttr->setValue(anIndex, *it);
+
+  if (!isPeriodic) {
+    AttributePoint2DPtr aStartPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+        aBSplineFeature->attribute(SketchPlugin_BSpline::START_ID()));
+    aStartPoint->setValue(aPolesAttr->pnt(0));
+
+    AttributePoint2DPtr aEndPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      aBSplineFeature->attribute(SketchPlugin_BSpline::END_ID()));
+    aEndPoint->setValue(aPolesAttr->pnt(aPolesAttr->size() - 1));
+  }
+
+  aBSplineFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->setValue(
+      boolean(AUXILIARY_ID())->value());
+
+  aBSplineFeature->data()->blockSendAttributeUpdated(aWasBlocked);
+
+  aBSplineFeature->execute();
+
+  return aBSplineFeature;
+}
+
+void SketchPlugin_CurveFitting::createConstraints(FeaturePtr theProducedFeature)
+{
+  if (!theProducedFeature)
+    return;
+
+  SketchPlugin_Sketch* aSketch = sketch();
+  ResultPtr aResult = theProducedFeature->lastResult();
+  bool isPeriodic = boolean(PERIODIC_ID())->value();
+  bool isClosed = boolean(CLOSED_ID())->value();
+  bool isApproximation = string(TYPE_ID())->value() == TYPE_APPROXIMATION_ID();
+
+  AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID());
+  std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = aPointsAttr->list();
+  std::list<std::pair<ObjectPtr, AttributePtr> >::iterator aLastIt = --aPointsList.end();
+  for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) {
+    AttributePtr anAttr = it->second;
+    if (!anAttr) {
+      // maybe the SketchPoint is selected
+      FeaturePtr aFeature = ModelAPI_Feature::feature(it->first);
+      if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID())
+        anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID());
+      else
+        continue;
+    }
+
+    if (!isPeriodic && it == aPointsList.begin()) {
+      SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::START_ID()));
+      if (isClosed) {
+        // end of B-spline curve should be coincident with the first selected point
+        SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
+            SketchPlugin_ConstraintCoincidence::ID(),
+            anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID()));
+      }
+    }
+    else if (!isPeriodic && !isClosed && it == aLastIt) {
+      SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID()));
+    }
+    else if (!isApproximation) {
+      SketchPlugin_Tools::createConstraintAttrObject(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          anAttr, aResult);
+    }
+  }
+}
+
+bool SketchPlugin_CurveFitting::customAction(const std::string& theActionId)
+{
+  bool isOk = true;
+  if (theActionId == REORDER_POINTS_ACTION_ID()) {
+    reorderPoints();
+  } else {
+    static const std::string MESSAGE("Error: Feature \"%1\" does not support action \"%2\".");
+    Events_InfoMessage("SketchPlugin_CurveFitting", MESSAGE)
+        .arg(getKind()).arg(theActionId).send();
+    isOk = false;
+  }
+  return isOk;
+}
+
+
+void SketchPlugin_CurveFitting::reorderPoints()
+{
+  AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID());
+  bool isPeriodic = boolean(PERIODIC_ID())->value();
+  bool isClosed = boolean(CLOSED_ID())->value();
+
+  std::list<GeomPointPtr> aCoordinates;
+  convertTo3D(sketch(), aPointsAttr, !isPeriodic && isClosed, aCoordinates);
+
+  // to keep mapping between points and attributes
+  std::map<GeomPointPtr, std::pair<ObjectPtr, AttributePtr> > aMap;
+  std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = aPointsAttr->list();
+  bool isPointAdded = aCoordinates.size() != aPointsList.size();
+  std::list<GeomPointPtr>::iterator aCoordIt = aCoordinates.begin();
+  std::list<std::pair<ObjectPtr, AttributePtr> >::iterator anAttrIt = aPointsList.begin();
+  for (; aCoordIt != aCoordinates.end() && anAttrIt != aPointsList.end(); ++aCoordIt, ++anAttrIt)
+    aMap[*aCoordIt] = *anAttrIt;
+
+  // reorder points
+  GeomAlgoAPI_CurveBuilder::reorderPoints(aCoordinates);
+
+  // re-compose the attribute
+  bool aWasBlocked = data()->blockSendAttributeUpdated(true);
+  aPointsAttr->clear();
+  for (aCoordIt = aCoordinates.begin(); aCoordIt != aCoordinates.end(); ++aCoordIt) {
+    const std::pair<ObjectPtr, AttributePtr>& aValue = aMap.at(*aCoordIt);
+    if (aValue.second)
+      aPointsAttr->append(aValue.second);
+    else
+      aPointsAttr->append(aValue.first);
+  }
+  data()->blockSendAttributeUpdated(aWasBlocked);
+}
+
+AISObjectPtr SketchPlugin_CurveFitting::getAISObject(AISObjectPtr thePrevious)
+{
+  SketchPlugin_Sketch* aSketch = sketch();
+  if (!aSketch)
+    return AISObjectPtr();
+
+  std::string aType = string(TYPE_ID())->value();
+  if (aType == TYPE_INTERPOLATION_ID()) {
+    myTransientResult = buildInterpolationCurve(aSketch,
+                                                refattrlist(POINTS_ID()),
+                                                boolean(PERIODIC_ID())->value(),
+                                                boolean(CLOSED_ID())->value());
+  }
+  else if (aType == TYPE_APPROXIMATION_ID()) {
+    myTransientResult = buildApproximationCurve(aSketch,
+                                                refattrlist(POINTS_ID()),
+                                                real(PRECISION_ID())->value(),
+                                                boolean(PERIODIC_ID())->value(),
+                                                boolean(CLOSED_ID())->value());
+  }
+  if (!myTransientResult)
+    return AISObjectPtr();
+
+  AISObjectPtr anAIS = thePrevious;
+  if (!anAIS)
+    anAIS.reset(new GeomAPI_AISObject());
+  anAIS->createShape(myTransientResult);
+
+  // Modify attributes
+  SketchPlugin_Tools::customizeFeaturePrs(anAIS, boolean(AUXILIARY_ID())->value());
+
+  return anAIS;
+}
+
+
+// ===============================     Auxiliary functions     ====================================
+
+void convertTo3D(SketchPlugin_Sketch* theSketch,
+                 const AttributeRefAttrListPtr& theAttribute,
+                 bool theClosedButNotPeriodic,
+                 std::list<GeomPointPtr>& thePoints)
+{
+  std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = theAttribute->list();
+  for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) {
+    AttributePtr anAttr = it->second;
+    if (!anAttr) {
+      // maybe the SketchPoint is selected
+      FeaturePtr aFeature = ModelAPI_Feature::feature(it->first);
+      if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID())
+        anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID());
+      else {
+        thePoints.clear();
+        break;
+      }
+    }
+
+    AttributePoint2DPtr aPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(anAttr);
+    if (aPoint) {
+      GeomPointPtr aPnt3D = theSketch->to3D(aPoint->x(), aPoint->y());
+      thePoints.push_back(aPnt3D);
+    }
+  }
+
+  if (theClosedButNotPeriodic && !thePoints.empty() &&
+      thePoints.front()->distance(thePoints.back()) > 1.e-7)
+    thePoints.push_back(thePoints.front()); // close the curve
+}
+
+GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch,
+                                    AttributeRefAttrListPtr thePoints,
+                                    bool thePeriodic,
+                                    bool theClosed)
+{
+  std::list<GeomPointPtr> aCoordinates;
+  convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates);
+
+  GeomEdgePtr aResult;
+  if (aCoordinates.size() > 1) {
+    static const bool isReorder = false;
+    static GeomDirPtr aStartEndDir;
+    aResult = GeomAlgoAPI_CurveBuilder::edge(aCoordinates, thePeriodic,
+                                             isReorder, aStartEndDir, aStartEndDir);
+  }
+  return aResult;
+}
+
+GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch,
+                                    AttributeRefAttrListPtr thePoints,
+                                    double thePrecision,
+                                    bool thePeriodic,
+                                    bool theClosed)
+{
+  std::list<GeomPointPtr> aCoordinates;
+  convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates);
+
+  GeomEdgePtr aResult;
+  if (aCoordinates.size() > 1)
+    aResult = GeomAlgoAPI_CurveBuilder::approximate(aCoordinates, thePeriodic, thePrecision);
+  return aResult;
+}
diff --git a/src/SketchPlugin/SketchPlugin_CurveFitting.h b/src/SketchPlugin/SketchPlugin_CurveFitting.h
new file mode 100644 (file)
index 0000000..d7c8b64
--- /dev/null
@@ -0,0 +1,154 @@
+// 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 SketchPlugin_CurveFitting_H_
+#define SketchPlugin_CurveFitting_H_
+
+#include <SketchPlugin.h>
+#include <SketchPlugin_SketchEntity.h>
+
+#include <GeomAPI_IPresentable.h>
+
+class GeomAPI_Edge;
+
+/**\class SketchPlugin_CurveFitting
+ * \ingroup Plugins
+ * \brief Feature for creation of the new B-spline curve in sketch
+ *        as an interpolation or an approximation of a list of points.
+ */
+class SketchPlugin_CurveFitting : public SketchPlugin_SketchEntity,
+                                  public GeomAPI_IPresentable
+{
+public:
+  /// Interpolation macro feature kind
+  inline static const std::string& ID()
+  {
+    static const std::string ID("SketchCurveFitting");
+    return ID;
+  }
+
+  /// list of selected points
+  inline static const std::string& POINTS_ID()
+  {
+    static const std::string ID("points");
+    return ID;
+  }
+
+  /// attribute for the periodic flag
+  inline static const std::string& PERIODIC_ID()
+  {
+    static const std::string ID("periodic");
+    return ID;
+  }
+
+  /// attribute for the closed flag
+  inline static const std::string& CLOSED_ID()
+  {
+    static const std::string ID("closed");
+    return ID;
+  }
+
+  /// attribute for the flag of creation a control polygon
+  inline static const std::string& NEED_CONTROL_POLYGON_ID()
+  {
+    static const std::string ID("need_control_poly");
+    return ID;
+  }
+
+  /// attribute for the type of the operation
+  inline static const std::string& TYPE_ID()
+  {
+    static const std::string ID("type");
+    return ID;
+  }
+
+  /// value for the type of operation
+  inline static const std::string& TYPE_INTERPOLATION_ID()
+  {
+    static const std::string ID("interpolation_type");
+    return ID;
+  }
+
+  /// value for the type of operation
+  inline static const std::string& TYPE_APPROXIMATION_ID()
+  {
+    static const std::string ID("approximation_type");
+    return ID;
+  }
+
+  /// attribute for the precision of the approximation
+  inline static const std::string& PRECISION_ID()
+  {
+    static const std::string ID("precision");
+    return ID;
+  }
+
+  /// attribute for the closed flag
+  inline static const std::string& REORDER_POINTS_ACTION_ID()
+  {
+    static const std::string ID("reorder_points");
+    return ID;
+  }
+
+  /// Returns the kind of a feature
+  SKETCHPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    static std::string MY_KIND = SketchPlugin_CurveFitting::ID();
+    return MY_KIND;
+  }
+
+  /// Returns the AIS preview
+  virtual AISObjectPtr getAISObject(AISObjectPtr thePrevious);
+
+  /// Creates a new part document if needed
+  SKETCHPLUGIN_EXPORT virtual void execute();
+
+  /// Reimplemented from ModelAPI_Feature::isMacro().
+  /// \returns true
+  SKETCHPLUGIN_EXPORT virtual bool isMacro() const {return true;};
+
+  SKETCHPLUGIN_EXPORT virtual bool isPreviewNeeded() const {return false;};
+
+  /// Performs some functionality by action id.
+  /// \param[in] theAttributeId action key id.
+  /// \return false in case if action not performed.
+  SKETCHPLUGIN_EXPORT virtual bool customAction(const std::string& theActionId);
+
+  /// Use plugin manager for features creation
+  SketchPlugin_CurveFitting();
+
+protected:
+  /// \brief Initializes attributes of derived class.
+  virtual void initDerivedClassAttributes();
+
+private:
+  /// \brief Create a feature, which passes through the selected points
+  FeaturePtr createBSplineFeature();
+
+  /// \brief Create coincidence constraints between selected points and the produced curve.
+  void createConstraints(FeaturePtr theProducedFeature);
+
+  /// \brief Reorder point to compose the polyline of the minimal length
+  void reorderPoints();
+
+private:
+  std::shared_ptr<GeomAPI_Edge> myTransientResult; ///< Interpolation curve
+};
+
+#endif
index f5ca951760467791ec47f1520de2726ae40e08ff..6ff0a30216d053d61726816b6a0dd2982ac810c8 100644 (file)
@@ -134,7 +134,7 @@ bool SketchPlugin_Ellipse::fillCharacteristicPoints()
     return false;
   }
 
-  data()->blockSendAttributeUpdated(true);
+  bool aWasBlocked = data()->blockSendAttributeUpdated(true);
   GeomPnt2dPtr aCenter2d = aCenterAttr->pnt();
   GeomPnt2dPtr aFocus2d = aFocusAttr->pnt();
   GeomDir2dPtr aMajorDir2d(new GeomAPI_Dir2d(aFocus2d->x() - aCenter2d->x(),
@@ -144,7 +144,9 @@ bool SketchPlugin_Ellipse::fillCharacteristicPoints()
   AttributeDoublePtr aMajorRadiusAttr = real(MAJOR_RADIUS_ID());
   double aFocalDist = aCenter2d->distance(aFocus2d);
   double aMajorRadius = sqrt(aFocalDist * aFocalDist + aMinorRadius * aMinorRadius);
-  aMajorRadiusAttr->setValue(aMajorRadius);
+  if (!aMajorRadiusAttr->isInitialized() ||
+      fabs(aMajorRadiusAttr->value() - aMajorRadius) > tolerance)
+    aMajorRadiusAttr->setValue(aMajorRadius);
 
   std::dynamic_pointer_cast<GeomDataAPI_Point2D>(attribute(SECOND_FOCUS_ID()))
     ->setValue(2.0 * aCenter2d->x() - aFocus2d->x(), 2.0 * aCenter2d->y() - aFocus2d->y());
@@ -160,7 +162,7 @@ bool SketchPlugin_Ellipse::fillCharacteristicPoints()
   std::dynamic_pointer_cast<GeomDataAPI_Point2D>(attribute(MINOR_AXIS_END_ID()))
       ->setValue(aCenter2d->x() + aMinorDir2d->x() * aMinorRadius,
                  aCenter2d->y() + aMinorDir2d->y() * aMinorRadius);
-  data()->blockSendAttributeUpdated(false);
+  data()->blockSendAttributeUpdated(aWasBlocked);
 
   return true;
 }
index 48158f1a826c86f48da608976153abbee07f3261..3d3ab4c53e7c92616bb209c0ab0b0085f1fe84c9 100644 (file)
@@ -84,7 +84,7 @@ void SketchPlugin_MacroBSpline::execute()
 
   if (boolean(CONTROL_POLYGON_ID())->value()) {
     std::list<FeaturePtr> aControlPoles;
-    createControlPolygon(aBSpline, aControlPoles);
+    createControlPolygon(aBSpline, myIsPeriodic, aControlPoles);
     constraintsForPoles(aControlPoles);
   }
 }
@@ -147,6 +147,7 @@ FeaturePtr SketchPlugin_MacroBSpline::createBSplineFeature()
 }
 
 void SketchPlugin_MacroBSpline::createControlPolygon(FeaturePtr theBSpline,
+                                                     bool thePeriodic,
                                                      std::list<FeaturePtr>& thePoles)
 {
   AttributePoint2DArrayPtr aPoles = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
@@ -158,7 +159,7 @@ void SketchPlugin_MacroBSpline::createControlPolygon(FeaturePtr theBSpline,
   // segments
   for (int index = 1; index < aSize; ++index)
     createAuxiliarySegment(aPoles, index - 1, index);
-  if (myIsPeriodic) {
+  if (thePeriodic) {
     // additional segment to close the control polygon
     createAuxiliarySegment(aPoles, aSize - 1, 0);
   }
index 9ecf63d49063d2a16b0853916792e35773ac2034..1c3aca6d7d7078e3b55eebf0dfc808af6851a9f6 100644 (file)
@@ -108,7 +108,12 @@ protected:
 private:
   FeaturePtr createBSplineFeature();
 
-  void createControlPolygon(FeaturePtr theBSpline, std::list<FeaturePtr>& thePoles);
+  /// Create control polygon for the B-spline and returns the list of its poles
+  static void createControlPolygon(FeaturePtr theBSpline,
+                                   bool thePeriodic,
+                                   std::list<FeaturePtr>& thePoles);
+
+  /// Create additional coincidences if other features were selected while creating the B-spline
   void constraintsForPoles(const std::list<FeaturePtr>& thePoles);
 
   /// Create Point feature coincident with the B-spline pole
@@ -124,6 +129,7 @@ private:
                                       const int thePoleIndex1,
                                       const int thePoleIndex2 = -1);
   friend class SketchPlugin_BSplineBase;
+  friend class SketchPlugin_CurveFitting;
 
 private:
   std::list<double> myKnots;
@@ -133,9 +139,9 @@ private:
 };
 
 
-/**\class SketchPlugin_MacroBSpline
+/**\class SketchPlugin_MacroBSplinePeriodic
 * \ingroup Plugins
-* \brief Feature for creation of the new B-spline in Sketch.
+* \brief Feature for creation of the new periodic B-spline in Sketch.
 */
 class SketchPlugin_MacroBSplinePeriodic : public SketchPlugin_MacroBSpline
 {
diff --git a/src/SketchPlugin/SketchPlugin_Offset.cpp b/src/SketchPlugin/SketchPlugin_Offset.cpp
new file mode 100644 (file)
index 0000000..f3f8d2b
--- /dev/null
@@ -0,0 +1,759 @@
+// 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 <SketchPlugin_Offset.h>
+
+#include <SketchPlugin_Sketch.h>
+#include <SketchPlugin_Line.h>
+#include <SketchPlugin_Point.h>
+#include <SketchPlugin_Arc.h>
+#include <SketchPlugin_Circle.h>
+#include <SketchPlugin_Ellipse.h>
+#include <SketchPlugin_EllipticArc.h>
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
+#include <SketchPlugin_Tools.h>
+
+#include <SketcherPrs_Factory.h>
+
+#include <Events_InfoMessage.h>
+
+#include <ModelAPI_AttributeBoolean.h>
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_AttributeIntArray.h>
+#include <ModelAPI_AttributeRefList.h>
+#include <ModelAPI_Events.h>
+#include <ModelAPI_ResultConstruction.h>
+#include <ModelAPI_Tools.h>
+#include <ModelAPI_Validator.h>
+
+#include <GeomAlgoAPI_MakeShapeList.h>
+#include <GeomAlgoAPI_Offset.h>
+#include <GeomAlgoAPI_ShapeTools.h>
+#include <GeomAlgoAPI_WireBuilder.h>
+
+#include <GeomAPI_BSpline.h>
+#include <GeomAPI_Circ.h>
+#include <GeomAPI_Edge.h>
+#include <GeomAPI_Ellipse.h>
+#include <GeomAPI_ShapeExplorer.h>
+#include <GeomAPI_Wire.h>
+#include <GeomAPI_WireExplorer.h>
+
+#include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
+
+static const double tolerance = 1.e-7;
+
+SketchPlugin_Offset::SketchPlugin_Offset()
+{
+}
+
+void SketchPlugin_Offset::initAttributes()
+{
+  data()->addAttribute(EDGES_ID(), ModelAPI_AttributeRefList::typeId());
+  data()->addAttribute(VALUE_ID(), ModelAPI_AttributeDouble::typeId());
+  data()->addAttribute(REVERSED_ID(), ModelAPI_AttributeBoolean::typeId());
+
+  // store original entities
+  data()->addAttribute(SketchPlugin_Constraint::ENTITY_A(), ModelAPI_AttributeRefList::typeId());
+  // store offset entities
+  data()->addAttribute(SketchPlugin_Constraint::ENTITY_B(), ModelAPI_AttributeRefList::typeId());
+  // store mapping between original entity and index of the corresponding offset entity
+  data()->addAttribute(SketchPlugin_Constraint::ENTITY_C(), ModelAPI_AttributeIntArray::typeId());
+
+  ModelAPI_Session::get()->validators()->
+      registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_A());
+  ModelAPI_Session::get()->validators()->
+      registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_B());
+  ModelAPI_Session::get()->validators()->
+      registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_C());
+}
+
+void SketchPlugin_Offset::execute()
+{
+  SketchPlugin_Sketch* aSketch = sketch();
+  if (!aSketch) return;
+
+  // 1. Sketch plane
+  std::shared_ptr<GeomAPI_Pln> aPlane = aSketch->plane();
+
+  // 2. Offset value
+  AttributeDoublePtr aValueAttr = real(VALUE_ID());
+  if (!aValueAttr->isInitialized()) return;
+  double aValue = aValueAttr->value();
+  if (aValue < tolerance) return;
+
+  // 2.a. Reversed?
+  AttributeBooleanPtr aReversedAttr = boolean(REVERSED_ID());
+  if (!aReversedAttr->isInitialized()) return;
+  if (aReversedAttr->value()) aValue = -aValue; // reverse offset direction
+
+  // 3. List of all selected edges
+  AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
+  std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
+
+  // 4. Put all selected edges in a set to pass them into findWireOneWay() below
+  std::set<FeaturePtr> anEdgesSet;
+  std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
+  for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
+    FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
+    if (aFeature) {
+      anEdgesSet.insert(aFeature);
+    }
+  }
+
+  // Wait all objects being created, then send update events
+  static Events_ID anUpdateEvent = Events_Loop::eventByName(EVENT_OBJECT_UPDATED);
+  bool isUpdateFlushed = Events_Loop::loop()->isFlushed(anUpdateEvent);
+  if (isUpdateFlushed)
+    Events_Loop::loop()->setFlushed(anUpdateEvent, false);
+
+  // 5. Gather wires and make offset for each wire
+  ListOfMakeShape anOffsetAlgos;
+  std::set<FeaturePtr> aProcessedEdgesSet;
+  for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
+    FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
+    if (aFeature.get()) {
+      if (aProcessedEdgesSet.find(aFeature) != aProcessedEdgesSet.end())
+        continue;
+
+      // 5.a. End points (if any)
+      std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
+      SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
+
+      // 5.b. Find a chain of edges
+      std::list<FeaturePtr> aChain;
+      aChain.push_back(aFeature);
+      bool isClosed = !(aStartPoint && anEndPoint);  // not closed edge
+      if (!isClosed) {
+        isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet,
+                                  aProcessedEdgesSet, aChain, true);
+        if (!isClosed) {
+          isClosed = findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet,
+                                    aProcessedEdgesSet, aChain, false);
+        }
+      }
+      aProcessedEdgesSet.insert(aFeature);
+
+      // 5.c. Make wire
+      ListOfShape aTopoChain;
+      std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
+      for (; aChainIt != aChain.end(); ++aChainIt) {
+        FeaturePtr aChainFeature = (*aChainIt);
+        GeomShapePtr aTopoEdge = aChainFeature->lastResult()->shape();
+        if (aTopoEdge->shapeType() == GeomAPI_Shape::EDGE) {
+          aTopoChain.push_back(aTopoEdge);
+        }
+      }
+      std::shared_ptr<GeomAlgoAPI_WireBuilder> aWireBuilder(
+          new GeomAlgoAPI_WireBuilder(aTopoChain, !isClosed));
+
+      GeomShapePtr aWireShape = aWireBuilder->shape();
+      GeomWirePtr aWire (new GeomAPI_Wire (aWireShape));
+
+      // Fix for a problem of offset side change with selection change.
+      // Wire direction is defined by the first selected edge of this wire.
+      double aSign = 1.;
+      if (!aWire->isClosed()) {
+        ListOfShape aModified;
+        // First selected edge of current chain
+        GeomShapePtr aFirstSel = aFeature->lastResult()->shape();
+        aWireBuilder->modified(aFirstSel, aModified);
+        GeomShapePtr aModFS = aModified.front();
+        if (aModFS->orientation() != aFirstSel->orientation())
+          aSign = -1.;
+      }
+
+      // 5.d. Make offset for the wire
+      std::shared_ptr<GeomAlgoAPI_Offset> anOffsetShape(
+          new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign));
+
+      std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList(new GeomAlgoAPI_MakeShapeList);
+      aMakeList->appendAlgo(aWireBuilder);
+      aMakeList->appendAlgo(anOffsetShape);
+      anOffsetAlgos.push_back(aMakeList);
+    }
+  }
+
+  // 6. Store offset results.
+  //    Create sketch feature for each edge of anOffsetShape, and also store
+  //    created features in CREATED_ID() to remove them on next execute()
+  addToSketch(anOffsetAlgos);
+
+  // send events to update the sub-features by the solver
+  if (isUpdateFlushed)
+    Events_Loop::loop()->setFlushed(anUpdateEvent, true);
+}
+
+bool SketchPlugin_Offset::findWireOneWay (const FeaturePtr& theFirstEdge,
+                                          const FeaturePtr& theEdge,
+                                          const std::shared_ptr<GeomDataAPI_Point2D>& theEndPoint,
+                                          std::set<FeaturePtr>& theEdgesSet,
+                                          std::set<FeaturePtr>& theProcessedEdgesSet,
+                                          std::list<FeaturePtr>& theChain,
+                                          const bool isPrepend)
+{
+  // 1. Find a single edge, coincident to theEndPoint by one of its ends
+  if (!theEndPoint) return false;
+
+  FeaturePtr aNextEdgeFeature;
+  int nbFound = 0;
+
+  std::set<AttributePoint2DPtr> aCoincPoints =
+      SketchPlugin_Tools::findPointsCoincidentToPoint(theEndPoint);
+
+  std::set<AttributePoint2DPtr>::iterator aPointsIt = aCoincPoints.begin();
+  for (; aPointsIt != aCoincPoints.end(); aPointsIt++) {
+    AttributePoint2DPtr aP = (*aPointsIt);
+    FeaturePtr aCoincFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aP->owner());
+    bool isInSet = (theEdgesSet.find(aCoincFeature) != theEdgesSet.end());
+
+    // Condition 0: not auxiliary
+    if (!isInSet && aCoincFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value())
+      continue;
+
+    // Condition 1: not a point feature
+    if (aCoincFeature->getKind() != SketchPlugin_Point::ID()) {
+      // Condition 2: it is not the current edge
+      if (aCoincFeature != theEdge) {
+        // Condition 3: it is in the set of interest.
+        //              Empty set means all sketch edges.
+        if (isInSet || theEdgesSet.empty()) {
+          // Condition 4: consider only features with two end points
+          std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
+          SketchPlugin_SegmentationTools::getFeaturePoints(aCoincFeature, aP1, aP2);
+          if (aP1 && aP2) {
+            // Condition 5: consider only features, that have aP as one of they ends.
+            //              For example, we do not need an arc, coincident to aP by its center.
+            if (theEndPoint->pnt()->isEqual(aP1->pnt()) ||
+                theEndPoint->pnt()->isEqual(aP2->pnt())) {
+              // Condition 6: only one edge can prolongate the chain. If several, we stop here.
+              nbFound++;
+              if (nbFound > 1)
+                return false;
+
+              // One found
+              aNextEdgeFeature = aCoincFeature;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Only one edge can prolongate the chain. If several or none, we stop here.
+  if (nbFound != 1)
+    return false;
+
+  // 2. So, we have the single edge, that prolongate the chain
+
+  // Condition 7: if we reached the very first edge of the chain
+  if (aNextEdgeFeature == theFirstEdge)
+    // Closed chain found
+    return true;
+
+  // 3. Add the found edge to the chain
+  if (isPrepend)
+    theChain.push_front(aNextEdgeFeature);
+  else
+    theChain.push_back(aNextEdgeFeature);
+  theProcessedEdgesSet.insert(aNextEdgeFeature);
+
+  // 4. Which end of aNextEdgeFeature we need to proceed
+  std::shared_ptr<GeomDataAPI_Point2D> aP1, aP2;
+  SketchPlugin_SegmentationTools::getFeaturePoints(aNextEdgeFeature, aP1, aP2);
+  if (aP2->pnt()->isEqual(theEndPoint->pnt())) {
+    // reversed
+    aP2 = aP1;
+  }
+
+  // 5. Continue gathering the chain (recursive)
+  return findWireOneWay (theFirstEdge, aNextEdgeFeature, aP2, theEdgesSet,
+                         theProcessedEdgesSet, theChain, isPrepend);
+}
+
+static void setRefListValue(AttributeRefListPtr theList, int theListSize,
+                            ObjectPtr theValue, int theIndex)
+{
+  if (theIndex < theListSize) {
+    ObjectPtr aCur = theList->object(theIndex);
+    if (aCur != theValue)
+      theList->substitute(aCur, theValue);
+  }
+  else
+    theList->append(theValue);
+}
+
+// Reorder shapes according to the wire's order
+static void reorderShapes(ListOfShape& theShapes, GeomShapePtr theWire)
+{
+  std::set<GeomShapePtr, GeomAPI_Shape::Comparator> aShapes;
+  aShapes.insert(theShapes.begin(), theShapes.end());
+  theShapes.clear();
+
+  GeomWirePtr aWire(new GeomAPI_Wire(theWire));
+  GeomAPI_WireExplorer anExp(aWire);
+  for (; anExp.more(); anExp.next()) {
+    GeomShapePtr aCurEdge = anExp.current();
+    auto aFound = aShapes.find(aCurEdge);
+    if (aFound != aShapes.end()) {
+      theShapes.push_back(aCurEdge);
+      aShapes.erase(aFound);
+    }
+  }
+}
+
+static void removeLastFromIndex(AttributeRefListPtr theList, int theListSize, int& theLastIndex)
+{
+  if (theLastIndex < theListSize) {
+    std::set<int> anIndicesToRemove;
+    for (; theLastIndex < theListSize; ++theLastIndex)
+      anIndicesToRemove.insert(theLastIndex);
+    theList->remove(anIndicesToRemove);
+  }
+}
+
+void SketchPlugin_Offset::addToSketch(const ListOfMakeShape& theOffsetAlgos)
+{
+  AttributeRefListPtr aSelectedRefList = reflist(EDGES_ID());
+  AttributeRefListPtr aBaseRefList = reflist(ENTITY_A());
+  AttributeRefListPtr anOffsetRefList = reflist(ENTITY_B());
+  AttributeIntArrayPtr anOffsetToBaseMap = intArray(ENTITY_C());
+
+  // compare the list of selected edges and the previously stored,
+  // and store maping between them
+  std::map<ObjectPtr, std::list<ObjectPtr> > aMapExistent;
+  std::list<ObjectPtr> anObjectsToRemove;
+  std::list<ObjectPtr> aSelectedList = aSelectedRefList->list();
+  for (std::list<ObjectPtr>::iterator aSIt = aSelectedList.begin();
+       aSIt != aSelectedList.end(); ++aSIt) {
+    aMapExistent[*aSIt] = std::list<ObjectPtr>();
+  }
+  for (int anIndex = 0, aSize = anOffsetRefList->size(); anIndex < aSize; ++anIndex) {
+    ObjectPtr aCurrent = anOffsetRefList->object(anIndex);
+    int aBaseIndex = anOffsetToBaseMap->value(anIndex);
+    if (aBaseIndex >= 0) {
+      ObjectPtr aBaseObj = aBaseRefList->object(aBaseIndex);
+      std::map<ObjectPtr, std::list<ObjectPtr> >::iterator aFound = aMapExistent.find(aBaseObj);
+      if (aFound != aMapExistent.end())
+        aFound->second.push_back(aCurrent);
+      else
+        anObjectsToRemove.push_back(aCurrent);
+    }
+    else
+      anObjectsToRemove.push_back(aCurrent);
+  }
+
+  // update lists of base shapes and of offset shapes
+  int aBaseListSize = aBaseRefList->size();
+  int anOffsetListSize = anOffsetRefList->size();
+  int aBaseListIndex = 0, anOffsetListIndex = 0;
+  std::list<int> anOffsetBaseBackRefs;
+  std::set<GeomShapePtr, GeomAPI_Shape::ComparatorWithOri> aProcessedOffsets;
+  for (std::list<ObjectPtr>::iterator aSIt = aSelectedList.begin();
+       aSIt != aSelectedList.end(); ++aSIt) {
+    // find an offseted edge
+    FeaturePtr aBaseFeature = ModelAPI_Feature::feature(*aSIt);
+    GeomShapePtr aBaseShape = aBaseFeature->lastResult()->shape();
+    ListOfShape aNewShapes;
+    for (ListOfMakeShape::const_iterator anAlgoIt = theOffsetAlgos.begin();
+         anAlgoIt != theOffsetAlgos.end(); ++anAlgoIt) {
+      (*anAlgoIt)->generated(aBaseShape, aNewShapes);
+      if (!aNewShapes.empty()) {
+        reorderShapes(aNewShapes, (*anAlgoIt)->shape());
+        break;
+      }
+    }
+
+    // store base feature
+    setRefListValue(aBaseRefList, aBaseListSize, *aSIt, aBaseListIndex);
+
+    // create or update an offseted feature
+    const std::list<ObjectPtr>& anImages = aMapExistent[*aSIt];
+    std::list<ObjectPtr>::const_iterator anImgIt = anImages.begin();
+    for (ListOfShape::iterator aNewIt = aNewShapes.begin(); aNewIt != aNewShapes.end(); ++aNewIt) {
+      FeaturePtr aNewFeature;
+      if (anImgIt != anImages.end())
+        aNewFeature = ModelAPI_Feature::feature(*anImgIt++);
+      updateExistentOrCreateNew(*aNewIt, aNewFeature, anObjectsToRemove);
+      aProcessedOffsets.insert(*aNewIt);
+
+      // store an offseted feature
+      setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
+
+      anOffsetBaseBackRefs.push_back(aBaseListIndex);
+      ++anOffsetListIndex;
+    }
+    ++aBaseListIndex;
+    anObjectsToRemove.insert(anObjectsToRemove.end(), anImgIt, anImages.end());
+  }
+  // create arcs generated from vertices
+  for (ListOfMakeShape::const_iterator anAlgoIt = theOffsetAlgos.begin();
+       anAlgoIt != theOffsetAlgos.end(); ++anAlgoIt) {
+    GeomShapePtr aCurWire = (*anAlgoIt)->shape();
+    GeomAPI_ShapeExplorer anExp(aCurWire, GeomAPI_Shape::EDGE);
+    for (; anExp.more(); anExp.next()) {
+      GeomShapePtr aCurEdge = anExp.current();
+      if (aProcessedOffsets.find(aCurEdge) == aProcessedOffsets.end()) {
+        FeaturePtr aNewFeature;
+        updateExistentOrCreateNew(aCurEdge, aNewFeature, anObjectsToRemove);
+        aProcessedOffsets.insert(aCurEdge);
+
+        // store an offseted feature
+        setRefListValue(anOffsetRefList, anOffsetListSize, aNewFeature, anOffsetListIndex);
+
+        anOffsetBaseBackRefs.push_back(-1);
+        ++anOffsetListIndex;
+      }
+    }
+  }
+
+  removeLastFromIndex(aBaseRefList, aBaseListSize, aBaseListIndex);
+  removeLastFromIndex(anOffsetRefList, anOffsetListSize, anOffsetListIndex);
+
+  anOffsetToBaseMap->setSize((int)anOffsetBaseBackRefs.size(), false);
+  int anIndex = 0;
+  for (std::list<int>::iterator anIt = anOffsetBaseBackRefs.begin();
+       anIt != anOffsetBaseBackRefs.end(); ++anIt) {
+    anOffsetToBaseMap->setValue(anIndex++, *anIt, false);
+  }
+
+  // remove unused objects
+  std::set<FeaturePtr> aSet;
+  for (std::list<ObjectPtr>::iterator anIt = anObjectsToRemove.begin();
+       anIt != anObjectsToRemove.end(); ++anIt) {
+    FeaturePtr aFeature = ModelAPI_Feature::feature(*anIt);
+    if (aFeature)
+      aSet.insert(aFeature);
+  }
+  ModelAPI_Tools::removeFeaturesAndReferences(aSet);
+}
+
+static void findOrCreateFeatureByKind(SketchPlugin_Sketch* theSketch,
+                                      const std::string& theFeatureKind,
+                                      FeaturePtr& theFeature,
+                                      std::list<ObjectPtr>& thePoolOfFeatures)
+{
+  if (theFeature) {
+    // check the feature type is the same as required
+    if (theFeature->getKind() != theFeatureKind) {
+      // return feature to the pool and try to find the most appropriate
+      thePoolOfFeatures.push_back(theFeature);
+      theFeature = FeaturePtr();
+    }
+  }
+  if (!theFeature) {
+    // try to find appropriate feature in the pool
+    for (std::list<ObjectPtr>::iterator it = thePoolOfFeatures.begin();
+         it != thePoolOfFeatures.end(); ++it) {
+      FeaturePtr aCurFeature = ModelAPI_Feature::feature(*it);
+      if (aCurFeature->getKind() == theFeatureKind) {
+        theFeature = aCurFeature;
+        thePoolOfFeatures.erase(it);
+        break;
+      }
+    }
+    // feature not found, create new
+    if (!theFeature)
+      theFeature = theSketch->addFeature(theFeatureKind);
+  }
+}
+
+void SketchPlugin_Offset::updateExistentOrCreateNew(const GeomShapePtr& theShape,
+                                                    FeaturePtr& theFeature,
+                                                    std::list<ObjectPtr>& thePoolOfFeatures)
+{
+  if (theShape->shapeType() != GeomAPI_Shape::EDGE)
+    return;
+
+  std::shared_ptr<GeomAPI_Edge> aResEdge(new GeomAPI_Edge(theShape));
+
+  std::shared_ptr<GeomAPI_Pnt2d> aFP, aLP;
+  std::shared_ptr<GeomAPI_Pnt> aFP3d = aResEdge->firstPoint();
+  std::shared_ptr<GeomAPI_Pnt> aLP3d = aResEdge->lastPoint();
+  if (aFP3d && aLP3d) {
+    aFP = sketch()->to2D(aFP3d);
+    aLP = sketch()->to2D(aLP3d);
+  }
+
+  if (aResEdge->isLine()) {
+    findOrCreateFeatureByKind(sketch(), SketchPlugin_Line::ID(), theFeature, thePoolOfFeatures);
+
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+      (theFeature->attribute(SketchPlugin_Line::START_ID()))->setValue(aFP);
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+      (theFeature->attribute(SketchPlugin_Line::END_ID()))->setValue(aLP);
+  }
+  else if (aResEdge->isArc()) {
+    std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
+    std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
+    std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
+
+    findOrCreateFeatureByKind(sketch(), SketchPlugin_Arc::ID(), theFeature, thePoolOfFeatures);
+
+    GeomDirPtr aCircNormal = aCircEdge->normal();
+    GeomDirPtr aSketchNormal = sketch()->coordinatePlane()->normal();
+    if (aSketchNormal->dot(aCircNormal) < -tolerance)
+      std::swap(aFP, aLP);
+
+    bool aWasBlocked = theFeature->data()->blockSendAttributeUpdated(true);
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+      (theFeature->attribute(SketchPlugin_Arc::END_ID()))->setValue(aLP);
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+      (theFeature->attribute(SketchPlugin_Arc::START_ID()))->setValue(aFP);
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+      (theFeature->attribute(SketchPlugin_Arc::CENTER_ID()))->setValue(aCP);
+    theFeature->data()->blockSendAttributeUpdated(aWasBlocked);
+  }
+  else if (aResEdge->isCircle()) {
+    std::shared_ptr<GeomAPI_Circ> aCircEdge = aResEdge->circle();
+    std::shared_ptr<GeomAPI_Pnt> aCP3d = aCircEdge->center();
+    std::shared_ptr<GeomAPI_Pnt2d> aCP = sketch()->to2D(aCP3d);
+
+    findOrCreateFeatureByKind(sketch(), SketchPlugin_Circle::ID(), theFeature, thePoolOfFeatures);
+
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+      (theFeature->attribute(SketchPlugin_Circle::CENTER_ID()))->setValue(aCP);
+    theFeature->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aCircEdge->radius());
+  }
+  else if (aResEdge->isEllipse()) {
+    std::shared_ptr<GeomAPI_Ellipse> anEllipseEdge = aResEdge->ellipse();
+
+    GeomPointPtr aCP3d = anEllipseEdge->center();
+    GeomPnt2dPtr aCP = sketch()->to2D(aCP3d);
+
+    GeomPointPtr aFocus3d = anEllipseEdge->firstFocus();
+    GeomPnt2dPtr aFocus = sketch()->to2D(aFocus3d);
+
+    if (aFP3d && aLP3d) {
+      // Elliptic arc
+      findOrCreateFeatureByKind(sketch(), SketchPlugin_EllipticArc::ID(),
+                                theFeature, thePoolOfFeatures);
+
+      bool aWasBlocked = theFeature->data()->blockSendAttributeUpdated(true);
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+        (theFeature->attribute(SketchPlugin_EllipticArc::CENTER_ID()))->setValue(aCP);
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+        (theFeature->attribute(SketchPlugin_EllipticArc::FIRST_FOCUS_ID()))->setValue(aFocus);
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+        (theFeature->attribute(SketchPlugin_EllipticArc::START_POINT_ID()))->setValue(aFP);
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+        (theFeature->attribute(SketchPlugin_EllipticArc::END_POINT_ID()))->setValue(aLP);
+      theFeature->data()->blockSendAttributeUpdated(aWasBlocked);
+    }
+    else {
+      // Ellipse
+      findOrCreateFeatureByKind(sketch(), SketchPlugin_Ellipse::ID(),
+                                theFeature, thePoolOfFeatures);
+
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+        (theFeature->attribute(SketchPlugin_Ellipse::CENTER_ID()))->setValue(aCP);
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>
+        (theFeature->attribute(SketchPlugin_Ellipse::FIRST_FOCUS_ID()))->setValue(aFocus);
+      theFeature->real(SketchPlugin_Ellipse::MINOR_RADIUS_ID())->setValue(
+        anEllipseEdge->minorRadius());
+    }
+  }
+  else {
+    // convert to b-spline
+    mkBSpline(theFeature, aResEdge, thePoolOfFeatures);
+  }
+
+  if (theFeature.get()) {
+    theFeature->boolean(SketchPlugin_SketchEntity::COPY_ID())->setValue(true);
+    theFeature->execute();
+
+    static Events_ID aRedisplayEvent = Events_Loop::eventByName(EVENT_OBJECT_TO_REDISPLAY);
+    ModelAPI_EventCreator::get()->sendUpdated(theFeature, aRedisplayEvent);
+    const std::list<ResultPtr>& aResults = theFeature->results();
+    for (std::list<ResultPtr>::const_iterator anIt = aResults.begin();
+         anIt != aResults.end(); ++anIt)
+      ModelAPI_EventCreator::get()->sendUpdated(*anIt, aRedisplayEvent);
+  }
+}
+
+void SketchPlugin_Offset::mkBSpline (FeaturePtr& theResult,
+                                     const GeomEdgePtr& theEdge,
+                                     std::list<ObjectPtr>& thePoolOfFeatures)
+{
+  GeomCurvePtr aCurve (new GeomAPI_Curve (theEdge));
+  // Forced conversion to b-spline, if aCurve is not b-spline
+  GeomBSplinePtr aBSpline = GeomAPI_BSpline::convertToBSpline(aCurve);
+
+  const std::string& aBSplineKind = aBSpline->isPeriodic() ? SketchPlugin_BSplinePeriodic::ID()
+                                                           : SketchPlugin_BSpline::ID();
+  findOrCreateFeatureByKind(sketch(), aBSplineKind, theResult, thePoolOfFeatures);
+
+  theResult->integer(SketchPlugin_BSpline::DEGREE_ID())->setValue(aBSpline->degree());
+
+  AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>
+    (theResult->attribute(SketchPlugin_BSpline::POLES_ID()));
+  std::list<GeomPointPtr> aPoles = aBSpline->poles();
+  aPolesAttr->setSize((int)aPoles.size());
+  std::list<GeomPointPtr>::iterator anIt = aPoles.begin();
+  for (int anIndex = 0; anIt != aPoles.end(); ++anIt, ++anIndex) {
+    GeomPnt2dPtr aPoleInSketch = sketch()->to2D(*anIt);
+    aPolesAttr->setPnt(anIndex, aPoleInSketch);
+  }
+
+  AttributeDoubleArrayPtr aWeightsAttr =
+      theResult->data()->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
+  std::list<double> aWeights = aBSpline->weights();
+  if (aWeights.empty()) { // rational B-spline
+    int aSize = (int)aPoles.size();
+    aWeightsAttr->setSize(aSize);
+    for (int anIndex = 0; anIndex < aSize; ++anIndex)
+      aWeightsAttr->setValue(anIndex, 1.0);
+  }
+  else { // non-rational B-spline
+    aWeightsAttr->setSize((int)aWeights.size());
+    std::list<double>::iterator anIt = aWeights.begin();
+    for (int anIndex = 0; anIt != aWeights.end(); ++anIt, ++anIndex)
+      aWeightsAttr->setValue(anIndex, *anIt);
+  }
+
+  AttributeDoubleArrayPtr aKnotsAttr =
+      theResult->data()->realArray(SketchPlugin_BSpline::KNOTS_ID());
+  std::list<double> aKnots = aBSpline->knots();
+  int aSize = (int)aKnots.size();
+  aKnotsAttr->setSize(aSize);
+  std::list<double>::iterator aKIt = aKnots.begin();
+  for (int index = 0; index < aSize; ++index, ++aKIt)
+    aKnotsAttr->setValue(index, *aKIt);
+
+  AttributeIntArrayPtr aMultsAttr =
+      theResult->data()->intArray(SketchPlugin_BSpline::MULTS_ID());
+  std::list<int> aMultiplicities = aBSpline->mults();
+  aSize = (int)aMultiplicities.size();
+  aMultsAttr->setSize(aSize);
+  std::list<int>::iterator aMIt = aMultiplicities.begin();
+  for (int index = 0; index < aSize; ++index, ++aMIt)
+    aMultsAttr->setValue(index, *aMIt);
+}
+
+void SketchPlugin_Offset::attributeChanged(const std::string& theID)
+{
+  if (theID == EDGES_ID()) {
+    AttributeRefListPtr aSelected = reflist(EDGES_ID());
+    if (aSelected->size() == 0) {
+      // Clear list of objects
+      AttributeRefListPtr anOffsetAttr = reflist(SketchPlugin_Constraint::ENTITY_B());
+      std::list<ObjectPtr> anOffsetList = anOffsetAttr->list();
+      std::set<FeaturePtr> aFeaturesToBeRemoved;
+      for (std::list<ObjectPtr>::iterator anIt = anOffsetList.begin();
+           anIt != anOffsetList.end(); ++anIt) {
+        FeaturePtr aFeature = ModelAPI_Feature::feature(*anIt);
+        if (aFeature)
+          aFeaturesToBeRemoved.insert(aFeature);
+      }
+
+      reflist(SketchPlugin_Constraint::ENTITY_A())->clear();
+      anOffsetAttr->clear();
+      intArray(SketchPlugin_Constraint::ENTITY_C())->setSize(0);
+
+      ModelAPI_Tools::removeFeaturesAndReferences(aFeaturesToBeRemoved);
+    }
+  }
+}
+
+bool SketchPlugin_Offset::customAction(const std::string& theActionId)
+{
+  bool isOk = false;
+  if (theActionId == ADD_WIRE_ACTION_ID()) {
+    isOk = findWires();
+  }
+  else {
+    std::string aMsg = "Error: Feature \"%1\" does not support action \"%2\".";
+    Events_InfoMessage("SketchPlugin_Offset", aMsg).arg(getKind()).arg(theActionId).send();
+  }
+  return isOk;
+}
+
+bool SketchPlugin_Offset::findWires()
+{
+  AttributeRefListPtr aSelectedEdges = reflist(EDGES_ID());
+  std::list<ObjectPtr> anEdgesList = aSelectedEdges->list();
+
+  // Empty set
+  std::set<FeaturePtr> anEdgesSet;
+
+  // Processed set
+  std::set<FeaturePtr> aProcessedEdgesSet;
+
+  // Put all selected edges in a set to avoid adding them in reflist(EDGES_ID())
+  std::set<FeaturePtr> aSelectedSet;
+  std::list<ObjectPtr>::const_iterator anEdgesIt = anEdgesList.begin();
+  for (; anEdgesIt != anEdgesList.end(); anEdgesIt++) {
+    FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
+    if (aFeature) {
+      aSelectedSet.insert(aFeature);
+    }
+  }
+
+  bool aWasBlocked = data()->blockSendAttributeUpdated(true);
+
+  // Gather chains of edges
+  for (anEdgesIt = anEdgesList.begin(); anEdgesIt != anEdgesList.end(); anEdgesIt++) {
+    FeaturePtr aFeature = ModelAPI_Feature::feature(*anEdgesIt);
+    if (aFeature.get()) {
+      if (aProcessedEdgesSet.find(aFeature) != aProcessedEdgesSet.end())
+        continue;
+      aProcessedEdgesSet.insert(aFeature);
+
+      // End points (if any)
+      std::shared_ptr<GeomDataAPI_Point2D> aStartPoint, anEndPoint;
+      SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint);
+
+      std::list<FeaturePtr> aChain;
+      aChain.push_back(aFeature);
+      bool isClosed = findWireOneWay(aFeature, aFeature, aStartPoint, anEdgesSet,
+                                     aProcessedEdgesSet, aChain, true);
+      if (!isClosed) {
+        findWireOneWay(aFeature, aFeature, anEndPoint, anEdgesSet,
+                       aProcessedEdgesSet, aChain, false);
+      }
+
+      std::list<FeaturePtr>::iterator aChainIt = aChain.begin();
+      for (; aChainIt != aChain.end(); ++aChainIt) {
+        FeaturePtr aChainFeature = (*aChainIt);
+        if (aSelectedSet.find(aChainFeature) == aSelectedSet.end()) {
+          aSelectedEdges->append(aChainFeature->lastResult());
+        }
+      }
+    }
+  }
+  // TODO: hilight selection in the viewer
+
+  data()->blockSendAttributeUpdated(aWasBlocked);
+  return true;
+}
+
+
+AISObjectPtr SketchPlugin_Offset::getAISObject(AISObjectPtr thePrevious)
+{
+  if (!sketch())
+    return thePrevious;
+
+  AISObjectPtr anAIS = SketcherPrs_Factory::offsetObject(this, sketch(),
+    thePrevious);
+  return anAIS;
+}
diff --git a/src/SketchPlugin/SketchPlugin_Offset.h b/src/SketchPlugin/SketchPlugin_Offset.h
new file mode 100644 (file)
index 0000000..3a809f3
--- /dev/null
@@ -0,0 +1,145 @@
+// 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 SketchPlugin_Offset_H_
+#define SketchPlugin_Offset_H_
+
+#include <SketchPlugin.h>
+#include <SketchPlugin_ConstraintBase.h>
+
+#include <GeomDataAPI_Point2D.h>
+
+#include <GeomAPI_Edge.h>
+
+class GeomAlgoAPI_MakeShape;
+
+/**\class SketchPlugin_Offset
+ * \ingroup Plugins
+ * \brief Builds offset curves in the sketch.
+ */
+class SketchPlugin_Offset : public SketchPlugin_ConstraintBase
+{
+public:
+  /// Offset macro feature kind
+  inline static const std::string& ID()
+  {
+    static const std::string ID("SketchOffset");
+    return ID;
+  }
+
+  /// Returns the kind of a feature
+  SKETCHPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    static std::string MY_KIND = SketchPlugin_Offset::ID();
+    return MY_KIND;
+  }
+
+  /// list of offset edges
+  inline static const std::string& EDGES_ID()
+  {
+    static const std::string ID("segments");
+    return ID;
+  }
+
+  /// attribute to store the offset value
+  inline static const std::string& VALUE_ID()
+  {
+    static const std::string ID("offset_value");
+    return ID;
+  }
+
+  /// attribute to store the reversed offset direction
+  inline static const std::string& REVERSED_ID()
+  {
+    static const std::string ID("reversed");
+    return ID;
+  }
+
+  /// name for add wire action
+  inline static const std::string& ADD_WIRE_ACTION_ID()
+  {
+    static const std::string ID("add_wire");
+    return ID;
+  }
+
+  /// Called on change of any argument-attribute of this object
+  SKETCHPLUGIN_EXPORT virtual void attributeChanged(const std::string& theID);
+
+  /// Creates a new part document if needed
+  SKETCHPLUGIN_EXPORT virtual void execute();
+
+  SKETCHPLUGIN_EXPORT virtual bool isPreviewNeeded() const { return true; }
+
+  /// Find edges connected by coincident boundary constraint and composing a wire with
+  /// the already selected segments. It means that not more than 2 edges can be connected
+  /// with the coincident point.
+  /// \param[in] theActionId action key id (in following form: Action#Index)
+  /// \return \c false in case the action not performed.
+  SKETCHPLUGIN_EXPORT virtual bool customAction(const std::string& theActionId);
+
+  /// Returns the AIS preview
+  SKETCHPLUGIN_EXPORT virtual AISObjectPtr getAISObject(AISObjectPtr thePrevious);
+
+  /// Use plugin manager for features creation.
+  SketchPlugin_Offset();
+
+protected:
+  /// \brief Initializes attributes of derived class.
+  virtual void initAttributes();
+
+private:
+  /// Find all wires connected with the selected edges
+  bool findWires();
+
+  /// Create sketch feature for each edge of the offset result,
+  /// and store it in ENTITY_B(). Original edges are copied to ENTITY_A() to update
+  /// correctly if original selection is modified.
+  void addToSketch (const std::list< std::shared_ptr<GeomAlgoAPI_MakeShape> >& theOffsetAlgos);
+
+  /// Create BSpline or BSplinePeriodic sketch feature from theEdge
+  void mkBSpline (FeaturePtr& theResult, const GeomEdgePtr& theEdge,
+                  std::list<ObjectPtr>& thePoolOfFeatures);
+
+  /// Update existent feature by the parameters of the given edge or create a new feature.
+  /// \param[in]     theShape          a shape to be converted to sketch feature
+  /// \param[in,out] theFeature        sketch feature to be updated or created from scratch
+  /// \param[in,out] thePoolOfFeatures list of features to be removed (may be used as a new feature)
+  void updateExistentOrCreateNew (const GeomShapePtr& theShape, FeaturePtr& theFeature,
+                                  std::list<ObjectPtr>& thePoolOfFeatures);
+
+  /// Find edges that prolongate theEdgeFeature (in a chain) at theEndPoint
+  /// Recursive method.
+  /// \param[in] theFirstEdge Start edge of wire searching
+  /// \param[in] theEdge Current edge
+  /// \param[in] theEndPoint Point of the Current edge, not belonging to a previous edge
+  /// \param[in/out] theEdgesSet All edges to find among. If empty, all sketch edges assumed.
+  /// \param[in/out] theProcessedEdgesSet Already processed (put in chains) edges.
+  /// \param[in/out] theChain Resulting edges
+  /// \param[in] isPrepend if true, push new found edges to theChain front, else to the back
+  /// \return \c true if the chain is closed
+  bool findWireOneWay (const FeaturePtr& theFirstEdge,
+                       const FeaturePtr& theEdge,
+                       const std::shared_ptr<GeomDataAPI_Point2D>& theEndPoint,
+                       std::set<FeaturePtr>& theEdgesSet,
+                       std::set<FeaturePtr>& theProcessedEdgesSet,
+                       std::list<FeaturePtr>& theChain,
+                       const bool isPrepend = false);
+};
+
+#endif
index 490a1d77d7a7d38c256e10ae5994083daef97374..b71ff3c37889f6dda4790a9aed34e0863bab772a 100644 (file)
@@ -51,6 +51,7 @@
 #include <SketchPlugin_MacroCircle.h>
 #include <SketchPlugin_MultiRotation.h>
 #include <SketchPlugin_MultiTranslation.h>
+#include <SketchPlugin_Offset.h>
 #include <SketchPlugin_Trim.h>
 #include <SketchPlugin_Split.h>
 #include <SketchPlugin_Validators.h>
@@ -61,6 +62,7 @@
 #include <SketchPlugin_MacroEllipticArc.h>
 #include <SketchPlugin_SketchDrawer.h>
 #include <SketchPlugin_SketchCopy.h>
+#include <SketchPlugin_CurveFitting.h>
 
 #include <SketcherPrs_Tools.h>
 
@@ -153,6 +155,8 @@ SketchPlugin_Plugin::SketchPlugin_Plugin()
                               new SketchPlugin_MultiRotationAngleValidator);
   aFactory->registerValidator("SketchPlugin_BSplineValidator",
                               new SketchPlugin_BSplineValidator);
+  aFactory->registerValidator("SketchPlugin_CurveFittingValidator",
+                              new SketchPlugin_CurveFittingValidator);
 
   // register this plugin
   ModelAPI_Session::get()->registerPlugin(this);
@@ -275,10 +279,14 @@ FeaturePtr SketchPlugin_Plugin::createFeature(std::string theFeatureID)
     return FeaturePtr(new SketchPlugin_EllipticArc);
   } else if (theFeatureID == SketchPlugin_MacroEllipticArc::ID()) {
     return FeaturePtr(new SketchPlugin_MacroEllipticArc);
+  } else if (theFeatureID == SketchPlugin_CurveFitting::ID()) {
+    return FeaturePtr(new SketchPlugin_CurveFitting);
   } else if (theFeatureID == SketchPlugin_SketchDrawer::ID()) {
     return FeaturePtr(new SketchPlugin_SketchDrawer);
   } else if (theFeatureID == SketchPlugin_SketchCopy::ID()) {
     return FeaturePtr(new SketchPlugin_SketchCopy);
+  } else if (theFeatureID == SketchPlugin_Offset::ID()) {
+    return FeaturePtr(new SketchPlugin_Offset);
   }
   // feature of such kind is not found
   return FeaturePtr();
@@ -356,6 +364,8 @@ std::shared_ptr<ModelAPI_FeatureStateMessage> SketchPlugin_Plugin
       aMsg->setState(SketchPlugin_MacroEllipticArc::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_ConstraintDistanceHorizontal::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_ConstraintDistanceVertical::ID(), aHasSketchPlane);
+      aMsg->setState(SketchPlugin_CurveFitting::ID(), aHasSketchPlane);
+      aMsg->setState(SketchPlugin_Offset::ID(), aHasSketchPlane);
       // SketchRectangle is a python feature, so its ID is passed just as a string
       aMsg->setState("SketchRectangle", aHasSketchPlane);
     }
index e0e6222bccc7da99c49019f70065687d0a3053c3..9133b197d9b7e41ad005435b53e336ff9f70454c 100644 (file)
@@ -35,6 +35,7 @@
 #include <ModelAPI_AttributeDouble.h>
 #include <ModelAPI_AttributeDoubleArray.h>
 #include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_AttributeString.h>
 #include <ModelAPI_ResultConstruction.h>
 #include <ModelAPI_Session.h>
 #include <ModelAPI_Validator.h>
 #include <cmath>
 
 static const double tolerance = 1.e-7;
+static const std::string THE_KEEP_REF("true");
+
+static bool isKeepReference(AttributeStringPtr theAttr)
+{
+  return !theAttr || !theAttr->isInitialized() || theAttr->value() == THE_KEEP_REF;
+}
 
 
 SketchPlugin_Projection::SketchPlugin_Projection()
@@ -71,6 +78,7 @@ void SketchPlugin_Projection::initDerivedClassAttributes()
 {
   data()->addAttribute(EXTERNAL_FEATURE_ID(), ModelAPI_AttributeSelection::typeId());
   data()->addAttribute(PROJECTED_FEATURE_ID(), ModelAPI_AttributeRefAttr::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), PROJECTED_FEATURE_ID());
   data()->attribute(PROJECTED_FEATURE_ID())->setIsArgument(false);
 
   data()->addAttribute(EXTERNAL_ID(), ModelAPI_AttributeSelection::typeId());
@@ -81,6 +89,22 @@ void SketchPlugin_Projection::initDerivedClassAttributes()
   ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), AUXILIARY_ID());
 }
 
+void SketchPlugin_Projection::initDerivedClassAttributes2()
+{
+  AttributePtr aKeepRefAttr =
+      data()->addAttribute(KEEP_REFERENCE_ID(), ModelAPI_AttributeString::typeId());
+  if (!aKeepRefAttr->isInitialized()) {
+    std::dynamic_pointer_cast<ModelAPI_AttributeString>(aKeepRefAttr)->setValue(THE_KEEP_REF);
+  }
+
+  data()->addAttribute(MAKE_FIXED(), ModelAPI_AttributeBoolean::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), MAKE_FIXED());
+
+  data()->addAttribute(FIXED_CONSTRAINT_ID(), ModelAPI_AttributeReference::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), FIXED_CONSTRAINT_ID());
+  data()->attribute(FIXED_CONSTRAINT_ID())->setIsArgument(false);
+}
+
 void SketchPlugin_Projection::execute()
 {
   AttributeRefAttrPtr aRefAttr = data()->refattr(PROJECTED_FEATURE_ID());
@@ -88,7 +112,7 @@ void SketchPlugin_Projection::execute()
     return;
   FeaturePtr aProjection = ModelAPI_Feature::feature(aRefAttr->object());
 
-  if (!lastResult().get()) {
+  if (isKeepReference(string(KEEP_REFERENCE_ID())) && !lastResult().get()) {
     bool hasProjResult = aProjection->lastResult().get() != NULL;
     ResultConstructionPtr aConstr = document()->createConstruction(data());
     if (hasProjResult)
@@ -106,6 +130,16 @@ void SketchPlugin_Projection::execute()
   computeProjection(EXTERNAL_FEATURE_ID());
 }
 
+bool SketchPlugin_Projection::isMacro() const
+{
+  if (!data() || !data()->isValid())
+    return false;
+
+  AttributeStringPtr aKeepRefAttr =
+      const_cast<SketchPlugin_Projection*>(this)->string(KEEP_REFERENCE_ID());
+  return !isKeepReference(aKeepRefAttr);
+}
+
 void SketchPlugin_Projection::attributeChanged(const std::string& theID)
 {
   if ((theID == EXTERNAL_FEATURE_ID() || theID == EXTERNAL_ID()) && !myIsComputing) {
@@ -233,25 +267,52 @@ void SketchPlugin_Projection::computeProjection(const std::string& theID)
   if (!isProjected)
     return; // projection is not computed, stop processing
 
-  aProjection->boolean(COPY_ID())->setValue(true);
+  bool keepBackRef = isKeepReference(string(KEEP_REFERENCE_ID()));
+
+  aProjection->boolean(COPY_ID())->setValue(keepBackRef);
   aProjection->execute();
   aRefAttr->setObject(aProjection);
 
   restoreCurrentFeature();
 
-  if (theID == EXTERNAL_FEATURE_ID()) {
-    selection(EXTERNAL_ID())->selectValue(aExtFeature);
+  AttributeBooleanPtr aMakeFixedAttr = boolean(MAKE_FIXED());
+  bool isMakeFixed = aMakeFixedAttr && aMakeFixedAttr->isInitialized() && aMakeFixedAttr->value();
+
+  AttributeReferencePtr aFixedConstrAttr = reference(FIXED_CONSTRAINT_ID());
+  FeaturePtr aFixedConstraint;
+  if (aFixedConstrAttr && aFixedConstrAttr->isInitialized())
+    aFixedConstraint = ModelAPI_Feature::feature(aFixedConstrAttr->value());
 
-    if (aResult) {
-      aResult->setShape(aProjection->lastResult()->shape());
-      setResult(aResult);
-      GeomShapePtr anEmptyVal;
-      aProjection->selection(EXTERNAL_ID())->setValue(lastResult(), anEmptyVal);
+  if (keepBackRef) {
+    if (theID == EXTERNAL_FEATURE_ID()) {
+      selection(EXTERNAL_ID())->selectValue(aExtFeature);
 
-      static const Events_ID anEvent = Events_Loop::eventByName(EVENT_VISUAL_ATTRIBUTES);
-      ModelAPI_EventCreator::get()->sendUpdated(aProjection, anEvent, false);
+      if (aResult) {
+        aResult->setShape(aProjection->lastResult()->shape());
+        setResult(aResult);
+        GeomShapePtr anEmptyVal;
+        aProjection->selection(EXTERNAL_ID())->setValue(lastResult(), anEmptyVal);
+      }
     }
   }
+  else if (isMakeFixed) {
+    // fix the projected entity with the Fixed constraint
+    if (!aFixedConstraint)
+      aFixedConstraint = sketch()->addFeature(SketchPlugin_ConstraintRigid::ID());
+    aFixedConstraint->refattr(SketchPlugin_Constraint::ENTITY_A())->setObject(
+                              aProjection->lastResult());
+  }
+
+
+  // remove Fixed constraint in case of redundance
+  if (aFixedConstraint && (keepBackRef || !isMakeFixed)) {
+    document()->removeFeature(aFixedConstraint);
+    aFixedConstraint = FeaturePtr();
+  }
+  aFixedConstrAttr->setValue(aFixedConstraint);
+
+  static const Events_ID anEvent = Events_Loop::eventByName(EVENT_VISUAL_ATTRIBUTES);
+  ModelAPI_EventCreator::get()->sendUpdated(aProjection, anEvent, false);
 }
 
 bool SketchPlugin_Projection::rebuildProjectedFeature(
index 64f7844b73ea76c353243aed8400abd41ad42725..aea956bc5fb72f683d138b3f166f8a05dc0d3d8e 100644 (file)
@@ -62,6 +62,24 @@ public:
     return MY_INCLUDE;
   }
 
+  static const std::string& KEEP_REFERENCE_ID()
+  {
+    static std::string ID("keep_reference");
+    return ID;
+  }
+
+  static const std::string& MAKE_FIXED()
+  {
+    static std::string ID("make_fixed");
+    return ID;
+  }
+
+  static const std::string& FIXED_CONSTRAINT_ID()
+  {
+    static std::string ID("fixed_constraint");
+    return ID;
+  }
+
   /// Returns true because projected feature is always external
   virtual bool isFixed()
   { return true; }
@@ -79,6 +97,11 @@ public:
   /// Called on change of any argument-attribute of this object: for external point
   SKETCHPLUGIN_EXPORT virtual void attributeChanged(const std::string& theID);
 
+  /// Returns true if this feature is used as macro: creates other features and then removed.
+  /// This feature may change its macro-state according to selected item.
+  /// \returns false by default
+  SKETCHPLUGIN_EXPORT virtual bool isMacro() const;
+
   /// Use plugin manager for features creation
   SketchPlugin_Projection();
 
@@ -86,6 +109,9 @@ protected:
   /// \brief Initializes attributes of derived class.
   virtual void initDerivedClassAttributes();
 
+  /// \brief Initializes attributes of keeping the reference to the original shape.
+  virtual void initDerivedClassAttributes2();
+
 private:
   /// \brief Find projection of a feature onto sketch plane
   void computeProjection(const std::string& theID);
index fb64f079c1e1b9fd7c2fbdc09e4bd272939eb835..e44941948ddef954ad378c36e30d6257c85540b5 100644 (file)
@@ -43,4 +43,7 @@ void SketchPlugin_SketchEntity::initAttributes()
   anAttr = data()->addAttribute(PARENT_ID(), ModelAPI_AttributeReference::typeId());
   anAttr->setIsArgument(false);
   ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), PARENT_ID());
+
+  // initialize the rest of attributes
+  initDerivedClassAttributes2();
 }
index c9a3e29a127a57c758660e73474a31ac433d9832..0fabd0bbb61cf2172f798354cd29f7d2e203e4db 100644 (file)
@@ -227,6 +227,9 @@ protected:
   /// \brief Initializes attributes of derived class.
   virtual void initDerivedClassAttributes(){};
 
+  /// \brief Initializes attributes of derived class which were added recently.
+  virtual void initDerivedClassAttributes2(){};
+
 };
 
 #endif
index bbdefcbdd0543a371bdb6e15956bf35b5268edbe..ed4084afe60dfdeab7e3325746e7afbee31275da 100644 (file)
@@ -20,6 +20,7 @@
 #include "SketchPlugin_Tools.h"
 
 #include "SketchPlugin_Arc.h"
+#include "SketchPlugin_BSpline.h"
 #include "SketchPlugin_Circle.h"
 #include "SketchPlugin_ConstraintCoincidence.h"
 #include "SketchPlugin_ConstraintCoincidenceInternal.h"
@@ -37,6 +38,7 @@
 #include <SketcherPrs_Tools.h>
 
 #include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeInteger.h>
 
 #include <ModelGeomAlgo_Point2D.h>
 #include <ModelGeomAlgo_Shape.h>
@@ -113,10 +115,10 @@ std::shared_ptr<GeomAPI_Pnt2d> getCoincidencePoint(const FeaturePtr theStartCoin
   return aPnt;
 }
 
-std::set<FeaturePtr> findCoincidentConstraints(const FeaturePtr& theFeature)
+std::set<FeaturePtr> findCoincidentConstraints(const ObjectPtr& theObject)
 {
   std::set<FeaturePtr> aCoincident;
-  const std::set<AttributePtr>& aRefsList = theFeature->data()->refsToMe();
+  const std::set<AttributePtr>& aRefsList = theObject->data()->refsToMe();
   std::set<AttributePtr>::const_iterator aIt;
   for (aIt = aRefsList.cbegin(); aIt != aRefsList.cend(); ++aIt) {
     FeaturePtr aConstrFeature = std::dynamic_pointer_cast<ModelAPI_Feature>((*aIt)->owner());
@@ -199,26 +201,29 @@ std::set<FeaturePtr> findFeaturesCoincidentToPoint(const AttributePoint2DPtr& th
 // Useful to find points coincident to a given point.
 class CoincidentPoints
 {
+  static const int THE_DEFAULT_INDEX = -1;
+
 public:
-  void addCoincidence(const AttributePoint2DPtr& thePoint1,
-                      const AttributePoint2DPtr& thePoint2 = AttributePoint2DPtr())
+  void addCoincidence(const AttributePtr& thePoint1, const int theIndex1,
+                      const AttributePtr& thePoint2, const int theIndex2)
   {
-    std::list< std::set<AttributePoint2DPtr> >::iterator aFound1 = find(thePoint1);
-    std::list< std::set<AttributePoint2DPtr> >::iterator aFound2 = find(thePoint2);
+    auto aFound1 = find(thePoint1, theIndex1);
+    auto aFound2 = find(thePoint2, theIndex2);
     if (aFound1 == myCoincidentPoints.end()) {
       if (aFound2 == myCoincidentPoints.end()) {
-        std::set<AttributePoint2DPtr> aNewSet;
-        aNewSet.insert(thePoint1);
+        std::map<AttributePtr, std::set<int> > aNewSet;
+        aNewSet[thePoint1].insert(theIndex1);
         if (thePoint2)
-          aNewSet.insert(thePoint2);
+          aNewSet[thePoint2].insert(theIndex2);
         myCoincidentPoints.push_back(aNewSet);
       } else
-        aFound2->insert(thePoint1);
+        (*aFound2)[thePoint1].insert(theIndex1);
     } else if (aFound2 == myCoincidentPoints.end()) {
       if (thePoint2)
-        aFound1->insert(thePoint2);
+        (*aFound1)[thePoint2].insert(theIndex2);
     } else {
-      aFound1->insert(aFound2->begin(), aFound2->end());
+      for (auto it = aFound2->begin(); it != aFound2->end(); ++it)
+        (*aFound1)[it->first].insert(it->second.begin(), it->second.end());
       myCoincidentPoints.erase(aFound2);
     }
   }
@@ -227,10 +232,35 @@ public:
   {
     collectCoincidentPoints(thePoint);
 
-    std::list< std::set<AttributePoint2DPtr> >::iterator aFound = find(thePoint);
-    if (aFound == myCoincidentPoints.end())
-      return std::set<AttributePoint2DPtr>();
-    return *aFound;
+    std::set<AttributePoint2DPtr> aCoincPoints;
+    auto aFound = find(thePoint, THE_DEFAULT_INDEX);
+    if (aFound != myCoincidentPoints.end()) {
+      for (auto it = aFound->begin(); it != aFound->end(); ++it) {
+        AttributePoint2DPtr aPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(it->first);
+        if (aPoint)
+          aCoincPoints.insert(aPoint);
+        else {
+          AttributePoint2DArrayPtr aPointArray =
+              std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(it->first);
+          if (aPointArray) {
+            // this is a B-spline feature, the connection is possible
+            // to the first or the last point
+            FeaturePtr anOwner = ModelAPI_Feature::feature(aPointArray->owner());
+            if (it->second.find(0) != it->second.end()) {
+              AttributePoint2DPtr aFirstPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+                  anOwner->attribute(SketchPlugin_BSpline::START_ID()));
+              aCoincPoints.insert(aFirstPoint);
+            }
+            if (it->second.find(aPointArray->size() - 1) != it->second.end()) {
+              AttributePoint2DPtr aFirstPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+                  anOwner->attribute(SketchPlugin_BSpline::END_ID()));
+              aCoincPoints.insert(aFirstPoint);
+            }
+          }
+        }
+      }
+    }
+    return aCoincPoints;
   }
 
 private:
@@ -239,7 +269,11 @@ private:
   {
     // iterate through coincideces for the given feature
     std::set<FeaturePtr> aCoincidences = SketchPlugin_Tools::findCoincidentConstraints(theFeature);
-    std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
+    if (theFeature->getKind() == SketchPlugin_Point::ID()) {
+      std::set<FeaturePtr> aCoincToRes =
+          SketchPlugin_Tools::findCoincidentConstraints(theFeature->lastResult());
+      aCoincidences.insert(aCoincToRes.begin(), aCoincToRes.end());
+    }\r    std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
     for (; aCIt != aCoincidences.end(); ++aCIt)
     {
       if (theCoincidences.find(*aCIt) != theCoincidences.end())
@@ -248,12 +282,18 @@ private:
       // iterate on coincident attributes
       for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
         AttributeRefAttrPtr aRefAttr = (*aCIt)->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
-        if (aRefAttr && !aRefAttr->isObject())
-        {
-          FeaturePtr anOwner = ModelAPI_Feature::feature(aRefAttr->attr()->owner());
-          if (anOwner != theFeature)
-            coincidences(anOwner, theCoincidences);
+        if (!aRefAttr)
+          continue;
+        FeaturePtr anOwner;
+        if (aRefAttr->isObject()) {
+          FeaturePtr aFeature = ModelAPI_Feature::feature(aRefAttr->object());
+          if (aFeature->getKind() == SketchPlugin_Point::ID())
+            anOwner = aFeature;
         }
+        else
+          anOwner = ModelAPI_Feature::feature(aRefAttr->attr()->owner());
+        if (anOwner && anOwner != theFeature)
+          coincidences(anOwner, theCoincidences);
       }
     }
   }
@@ -262,7 +302,8 @@ private:
   // (two points may be coincident through the third point)
   void collectCoincidentPoints(const AttributePoint2DPtr& thePoint)
   {
-    AttributePoint2DPtr aPoints[2];
+    AttributePtr aPoints[2];
+    int anIndicesInArray[2];
 
     FeaturePtr anOwner = ModelAPI_Feature::feature(thePoint->owner());
     std::set<FeaturePtr> aCoincidences;
@@ -270,30 +311,69 @@ private:
 
     std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
     for (; aCIt != aCoincidences.end(); ++aCIt) {
-      aPoints[0] = AttributePoint2DPtr();
-      aPoints[1] = AttributePoint2DPtr();
+      aPoints[0] = aPoints[1] = AttributePtr();
+      anIndicesInArray[0] = anIndicesInArray[1] = THE_DEFAULT_INDEX;
       for (int i = 0, aPtInd = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
         AttributeRefAttrPtr aRefAttr = (*aCIt)->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
-        if (aRefAttr && !aRefAttr->isObject())
-          aPoints[aPtInd++] = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(aRefAttr->attr());
+        if (!aRefAttr)
+          continue;
+        if (aRefAttr->isObject()) {
+          FeaturePtr aFeature = ModelAPI_Feature::feature(aRefAttr->object());
+          if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID())
+            aPoints[aPtInd++] = aFeature->attribute(SketchPlugin_Point::COORD_ID());
+        }
+        else {
+          AttributePoint2DPtr aPointAttr =
+              std::dynamic_pointer_cast<GeomDataAPI_Point2D>(aRefAttr->attr());
+          AttributePoint2DArrayPtr aPointArray =
+              std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(aRefAttr->attr());
+          if (aPointAttr)
+            aPoints[aPtInd++] = aPointAttr;
+          else if (aPointArray) {
+            AttributeIntegerPtr anIndexAttr = (*aCIt)->integer(i == 0 ?
+                SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_A() :
+                SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B());
+            aPoints[aPtInd] = aPointArray;
+            anIndicesInArray[aPtInd++] = anIndexAttr->value();
+          }
+        }
       }
 
       if (aPoints[0] && aPoints[1])
-        addCoincidence(aPoints[0], aPoints[1]);
+        addCoincidence(aPoints[0], anIndicesInArray[0], aPoints[1], anIndicesInArray[1]);
     }
   }
 
-  std::list< std::set<AttributePoint2DPtr> >::iterator find(const AttributePoint2DPtr& thePoint)
+  std::list< std::map<AttributePtr, std::set<int> > >::iterator find(const AttributePtr& thePoint,
+                                                                     const int theIndex)
   {
-    std::list< std::set<AttributePoint2DPtr> >::iterator aSeek = myCoincidentPoints.begin();
-    for (; aSeek != myCoincidentPoints.end(); ++aSeek)
-      if (aSeek->find(thePoint) != aSeek->end())
+    auto aSeek = myCoincidentPoints.begin();
+    for (; aSeek != myCoincidentPoints.end(); ++aSeek) {
+      auto aFound = aSeek->find(thePoint);
+      if (aFound != aSeek->end() && aFound->second.find(theIndex) != aFound->second.end())
         return aSeek;
+    }
+    // nothing is found, but if the point is a B-spline boundary point, lets check it as poles array
+    FeaturePtr anOwner = ModelAPI_Feature::feature(thePoint->owner());
+    if (anOwner->getKind() == SketchPlugin_BSpline::ID()) {
+      AttributePtr aPointsArray;
+      int anIndex = -1;
+      if (thePoint->id() == SketchPlugin_BSpline::START_ID()) {
+        aPointsArray = anOwner->attribute(SketchPlugin_BSpline::POLES_ID());
+        anIndex = 0;
+      }
+      else if (thePoint->id() == SketchPlugin_BSpline::END_ID()) {
+        aPointsArray = anOwner->attribute(SketchPlugin_BSpline::POLES_ID());
+        anIndex = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(aPointsArray)->size() - 1;
+      }
+      if (aPointsArray)
+        return find(aPointsArray, anIndex);
+    }
     return myCoincidentPoints.end();
   }
 
 private:
-  std::list< std::set<AttributePoint2DPtr> > myCoincidentPoints;
+  std::list< std::map<AttributePtr, std::set<int> > > myCoincidentPoints;
 };
 
 std::set<AttributePoint2DPtr> findPointsCoincidentToPoint(const AttributePoint2DPtr& thePoint)
@@ -302,6 +382,7 @@ std::set<AttributePoint2DPtr> findPointsCoincidentToPoint(const AttributePoint2D
   return aCoincidentPoints.coincidentPoints(thePoint);
 }
 
+
 void resetAttribute(SketchPlugin_Feature* theFeature,
                     const std::string& theId)
 {
@@ -600,6 +681,10 @@ void SketchPlugin_SegmentationTools::getFeaturePoints(const FeaturePtr& theFeatu
     aStartAttributeName = SketchPlugin_EllipticArc::START_POINT_ID();
     anEndAttributeName = SketchPlugin_EllipticArc::END_POINT_ID();
   }
+  else if (aFeatureKind == SketchPlugin_BSpline::ID()) {
+    aStartAttributeName = SketchPlugin_BSpline::START_ID();
+    anEndAttributeName = SketchPlugin_BSpline::END_ID();
+  }
   if (!aStartAttributeName.empty() && !anEndAttributeName.empty()) {
     theStartPointAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
         theFeature->attribute(aStartAttributeName));
index eb9949e7d623fe824976d13e69a95d8dada4553d..eb86f8b33702e49bfae9ea79f52d9d2ca8584ec6 100644 (file)
@@ -28,6 +28,7 @@
 #include <GeomAPI_Shape.h>
 #include <GeomAPI_AISObject.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 #include <GeomAlgoAPI_ShapeTools.h>
 
 #include <list>
@@ -49,7 +50,7 @@ void clearExpressions(FeaturePtr theFeature);
 std::shared_ptr<GeomAPI_Pnt2d> getCoincidencePoint(const FeaturePtr theStartCoin);
 
 /// Find all Coincident constraints referred to the feature or its attribute
-std::set<FeaturePtr> findCoincidentConstraints(const FeaturePtr& theFeature);
+std::set<FeaturePtr> findCoincidentConstraints(const ObjectPtr& theObject);
 
 /// Finds lines coincident at point
 /// \param[in] theStartCoin coincidence feature
index b09094fd5245d3a154608984283ea301650756fc..e226f229e8a22af9f84b98dc27a5c414fa6adf7f 100644 (file)
 #include "SketchPlugin_Ellipse.h"
 #include "SketchPlugin_EllipticArc.h"
 #include "SketchPlugin_Fillet.h"
+#include "SketchPlugin_CurveFitting.h"
 #include "SketchPlugin_Line.h"
 #include "SketchPlugin_MacroArc.h"
 #include "SketchPlugin_MacroCircle.h"
 #include "SketchPlugin_MultiRotation.h"
+#include "SketchPlugin_Offset.h"
 #include "SketchPlugin_Point.h"
 #include "SketchPlugin_Sketch.h"
 #include "SketchPlugin_Trim.h"
@@ -49,6 +51,7 @@
 #include <ModelAPI_AttributeDouble.h>
 #include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_AttributeRefAttr.h>
+#include <ModelAPI_AttributeRefAttrList.h>
 #include <ModelAPI_AttributeRefList.h>
 #include <ModelAPI_AttributeSelectionList.h>
 #include <ModelAPI_AttributeString.h>
@@ -518,6 +521,7 @@ bool SketchPlugin_CopyValidator::isValid(const AttributePtr& theAttribute,
   FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theAttribute->owner());
   AttributeRefListPtr aSelAttr =
     std::dynamic_pointer_cast<ModelAPI_AttributeRefList>(theAttribute);
+  std::set<ObjectPtr> aSelected;
 
   AttributeRefListPtr aRefListOfInitial = std::dynamic_pointer_cast<ModelAPI_AttributeRefList>(
       aFeature->attribute(SketchPlugin_Constraint::ENTITY_A()));
@@ -529,6 +533,12 @@ bool SketchPlugin_CopyValidator::isValid(const AttributePtr& theAttribute,
   std::list<ObjectPtr>::iterator anObjIter;
   for(int anInd = 0; anInd < aSelAttr->size(); anInd++) {
     ObjectPtr aSelObject = aSelAttr->object(anInd);
+    if (aSelected.find(aSelObject) != aSelected.end()) {
+      theError = "Error: An object selected twice";
+      return false;
+    }
+    aSelected.insert(aSelObject);
+
     anObjIter = anInitialObjects.begin();
     for (; anObjIter != anInitialObjects.end(); anObjIter++)
       if (aSelObject == *anObjIter)
@@ -538,20 +548,34 @@ bool SketchPlugin_CopyValidator::isValid(const AttributePtr& theAttribute,
 
     // B-splines are not supported in Copying features
     FeaturePtr aSelFeature = ModelAPI_Feature::feature(aSelObject);
-    if (aSelFeature && (aSelFeature->getKind() == SketchPlugin_BSpline::ID() ||
+    if (aFeature->getKind() != SketchPlugin_Offset::ID() &&
+        aSelFeature && (aSelFeature->getKind() == SketchPlugin_BSpline::ID() ||
         aSelFeature->getKind() == SketchPlugin_BSplinePeriodic::ID())) {
       theError = "Not supported";
       return false;
     }
 
     anObjIter = aCopiedObjects.begin();
-    for (; anObjIter != aCopiedObjects.end(); anObjIter++)
-      if (aSelObject == *anObjIter) {
+    for (; anObjIter != aCopiedObjects.end(); anObjIter++) {
+      bool isFound = aSelObject == *anObjIter;
+      if (!isFound) {
+        // check in the results of the feature
+        FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(*anObjIter);
+        if (aFeature) {
+          const std::list<ResultPtr>& aResults = aFeature->results();
+          for (std::list<ResultPtr>::const_iterator aResIt = aResults.begin();
+            aResIt != aResults.end() && !isFound; ++aResIt) {
+            isFound = aSelObject == *aResIt;
+          }
+        }
+      }
+      if (isFound) {
         std::string aName = aSelObject.get() ? aSelObject->data()->name() : "";
         theError = "The object %1 is a result of copy";
         theError.arg(aName);
         return false;
       }
+    }
   }
   return true;
 }
@@ -1242,7 +1266,7 @@ bool SketchPlugin_ProjectionValidator::isValid(const AttributePtr& theAttribute,
     std::shared_ptr<GeomAPI_Ellipse> anEllipse = anEdge->ellipse();
     std::shared_ptr<GeomAPI_Dir> anEllipseNormal = anEllipse->normal();
     double aDot = fabs(aNormal->dot(anEllipseNormal));
-    aValid = fabs(aDot - 1.0) <= tolerance * tolerance;
+    aValid = aDot >= tolerance * tolerance;
     if (!aValid)
       theError.arg(anEdge->isClosed() ? "Error: Ellipse is orthogonal to the sketch plane."
                                       : "Error: Elliptic Arc is orthogonal to the sketch plane.");
@@ -1937,3 +1961,22 @@ bool SketchPlugin_BSplineValidator::isValid(const AttributePtr& theAttribute,
 
   return true;
 }
+
+bool SketchPlugin_CurveFittingValidator::isValid(const FeaturePtr& theFeature,
+                                                 const std::list<std::string>& theArguments,
+                                                 Events_InfoMessage& theError) const
+{
+  AttributeRefAttrListPtr aRefAttrList =
+      theFeature->refattrlist(SketchPlugin_CurveFitting::POINTS_ID());
+  AttributeBooleanPtr aPeriodicAttr =
+      theFeature->boolean(SketchPlugin_CurveFitting::PERIODIC_ID());
+
+  // check number of selected entities
+  int aMinNbPoints = aPeriodicAttr->value() ? 3 : 2;
+  if (aRefAttrList->size() < aMinNbPoints) {
+    theError = "Not enough points selected. Need at least %1 points.";
+    theError.arg(aMinNbPoints);
+    return false;
+  }
+  return true;
+}
index 1f5a8b846665021ae761a3db625371bfce3d8ad7..01c1f4f3a739524e9034781006d9919b7613b381 100644 (file)
@@ -546,4 +546,20 @@ class SketchPlugin_BSplineValidator : public ModelAPI_AttributeValidator
                        Events_InfoMessage& theError) const;
 };
 
+/**\class SketchPlugin_CurveFittingValidator
+ * \ingroup Validators
+ * \brief Validator for the selected vertices for the curve fitting feature.
+ */
+class SketchPlugin_CurveFittingValidator : public ModelAPI_FeatureValidator
+{
+public:
+  //! returns true if number of selected points is greater than the minimal value
+  //! \param theAttribute the checked attribute
+  //! \param theArguments arguments of the attribute
+  //! \param theError error message
+  virtual bool isValid(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                       const std::list<std::string>& theArguments,
+                       Events_InfoMessage& theError) const;
+};
+
 #endif
index b2271f3eaf956b761204531293791f1ab20571d8..f88fab7538aa76ece5295f3b289dbcd076e97813 100644 (file)
@@ -1,6 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
 <TS version="2.0" language="en_US">
+
+  <context>
+    <name>workshop</name>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Curve fitting</translation>
+    </message>
+  </context>
+
   <context>
     <name>Sketch:Model_FeatureValidator</name>
     <message>
       <translation>Edges in selected point has tangent constraint</translation>
     </message>
   </context>
-  
+
+  <context>
+    <name>SketchProjection</name>
+    <message>
+      <source>Project feature onto sketch plane</source>
+      <translation>Project feature onto sketch plane</translation>
+    </message>
+    <message>
+      <source>Projection</source>
+      <translation>Projection</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:ExternalFeature</name>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Select external edge or vertex.</translation>
+    </message>
+    <message>
+      <source>Object</source>
+      <translation>Object</translation>
+    </message>
+    <message>
+      <source>Select external edge or vertex.</source>
+      <translation>Select external edge or vertex.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:IncludeToResult</name>
+    <message>
+      <source>Include into the sketch result</source>
+      <translation>Include into the sketch result</translation>
+    </message>
+    <message>
+      <source>Include projected feature into the sketch result</source>
+      <translation>Include projected feature into the sketch result</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:keep_reference</name>
+    <message>
+      <source>A sketch entity will be created without connection to the selected shape.</source>
+      <translation>A sketch entity will be created without connection to the selected shape.</translation>
+    </message>
+    <message>
+      <source>The reference to the original curve is stored. So it can be changed later.</source>
+      <translation>The reference to the original curve is stored. So it can be changed later.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:make_fixed</name>
+    <message>
+      <source>Assign the Fixed constraint to the result of projection</source>
+      <translation>Assign the Fixed constraint to the result of projection</translation>
+    </message>
+    <message>
+      <source>Make projected curve fixed</source>
+      <translation>Make projected curve fixed</translation>
+    </message>
+  </context>
   <context>
     <name>SketchProjection:ExternalFeature:SketchPlugin_ProjectionValidator</name>
     <message>
       <translation>Error: Selected object is not supported for projection.</translation>
     </message>
   </context>
+
+  <!-- SketchCurveFitting -->
+  <context>
+    <name>SketchCurveFitting</name>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Curve fitting</translation>
+    </message>
+    <message>
+      <source>Create curve passing through the points</source>
+      <translation>Create curve passing through the points</translation>
+    </message>
+    <message>
+      <source>Error: Feature "%1" does not support action "%2".</source>
+      <translation>Error: Feature "%1" does not support action "%2".</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:SketchPlugin_CurveFittingValidator</name>
+    <message>
+      <source>Not enough points selected. Need at least %1 points.</source>
+      <translation>Not enough points selected. Need at least %1 points.</translation>
+    </message>
+  </context>
   <context>
-    <name>SketchProjection:Model_FeatureValidator</name>
+    <name>SketchCurveFitting:points</name>
+    <message>
+      <source>Points</source>
+      <translation>Points</translation>
+    </message>
+    <message>
+      <source>Select points for curve fitting</source>
+      <translation>Select points for curve fitting</translation>
+    </message>
     <message>
       <source>Attribute "%1" is not initialized.</source>
-      <translation></translation>
+      <translation>Select points for interpolation</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:periodic</name>
+    <message>
+      <source>Periodic</source>
+      <translation>Periodic</translation>
+    </message>
+    <message>
+      <source>Make curve periodic</source>
+      <translation>Make curve periodic</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:closed</name>
+    <message>
+      <source>Closed</source>
+      <translation>Closed</translation>
+    </message>
+    <message>
+      <source>Make curve closed, but not periodic</source>
+      <translation>Make curve closed, but not periodic</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:type</name>
+    <message>
+      <source>Interpolation</source>
+      <translation>Interpolation</translation>
+    </message>
+    <message>
+      <source>Approximation</source>
+      <translation>Approximation</translation>
     </message>
   </context>
+  <context>
+    <name>SketchCurveFitting:precision</name>
+    <message>
+      <source>Precision</source>
+      <translation>Precision</translation>
+    </message>
+    <message>
+      <source>Maximal distance from selected points to the curve</source>
+      <translation>Maximal distance from selected points to the curve</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:need_control_poly</name>
+    <message>
+      <source>Create control polygon</source>
+      <translation>Create control polygon</translation>
+    </message>
+    <message>
+      <source>Specify if the control polygon should be created</source>
+      <translation>Specify if the control polygon should be created</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:reorder_points</name>
+    <message>
+      <source>Reorder points</source>
+      <translation>Reorder points</translation>
+    </message>
+    <message>
+      <source>Sort selected points to minimize the distance heighbors</source>
+      <translation>Sort selected points to minimize the distance heighbors</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:Auxiliary</name>
+    <message>
+      <source>Auxiliary</source>
+      <translation>Auxiliary</translation>
+    </message>
+    <message>
+      <source>Construction element</source>
+      <translation>Construction element</translation>
+    </message>
+  </context>
+
 </TS>
index 369a4050f432c4d39c458981f840a1b2f82fd2c3..74e55917088470edc0cf85ec69fcbf913ad3729d 100644 (file)
       <source>Horizontal Distance</source>
       <translation>Distance horizontale</translation>
     </message>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Courbe d&apos;ajustement</translation>
+    </message>
     <message>
       <source>Length</source>
       <translation>Longueur</translation>
     </message>
   </context>
 
-  <context>
-    <name>SketchProjection:ExternalFeature:SketchPlugin_ProjectionValidator</name>
-    <message>
-      <source>The attribute with the %1 type is not processed</source>
-      <translation>Un argument de type %1 de la fonctionnalité de projection n&apos;est pas pris en charge</translation>
-    </message>
-    <message>
-      <source>The attribute %1 should be an edge</source>
-      <translation>L&apos;attribut %1 doit être une arête</translation>
-    </message>
-    <message>
-      <source>There is no sketch referring to the current feature</source>
-      <translation>La fonction de projection n&apos;a pas d&apos;esquisse</translation>
-    </message>
-    <message>
-      <source>The attribute %1 should be an edge or vertex</source>
-      <translation>L&apos;élément projeté doit être une arête ou un sommet</translation>
-    </message>
-    <message>
-      <source>Unable to project feature from the same sketch</source>
-      <translation>Les fonctions de l&apos;esquisse en cours ne peuvent pas être projetées</translation>
-    </message>
-    <message>
-      <source>Error: Line is orthogonal to the sketch plane.</source>
-      <translation>Erreur: La ligne est orthogonale au plan d&apos;esquisse.</translation>
-    </message>
-    <message>
-      <source>Error: Circle is orthogonal to the sketch plane.</source>
-      <translation>Erreur: Le cercle est orthogonal au plan d&apos;esquisse.</translation>
-    </message>
-    <message>
-      <source>Error: Arc is orthogonal to the sketch plane.</source>
-      <translation>Erreur: L&apos;arc est orthogonal au plan d&apos;esquisse.</translation>
-    </message>
-    <message>
-      <source>Error: Ellipse is orthogonal to the sketch plane.</source>
-      <translation>Erreur: L&apos;ellipse est orthogonale au plan d&apos;esquisse.</translation>
-    </message>
-    <message>
-      <source>Error: Elliptic Arc is orthogonal to the sketch plane.</source>
-      <translation>Erreur: L&apos;arc d&apos;ellipse est orthogonal au plan d&apos;esquisse.</translation>
-    </message>
-    <message>
-      <source>Error: Periodic B-spline is orthogonal to the sketch plane.</source>
-      <translation>Erreur: La B-spline périodique est orthogonale au plan d&apos;esquisse.</translation>
-    </message>
-    <message>
-      <source>Error: Selected object is not supported for projection.</source>
-      <translation>Erreur: L&apos;objet sélectionné n&apos;est pas pris en charge pour la projection.</translation>
-    </message>
-  </context>
-  <context>
-    <name>SketchProjection:Model_FeatureValidator</name>
-    <message>
-      <source>Attribute "%1" is not initialized.</source>
-      <translation>L&apos;attribut &quot;%1&quot; n&apos;est pas initialisé.</translation>
-    </message>
-  </context>
-  <context>
-    <name>SketchProjection:ExternalFeature</name>
-    <message>
-      <source>Attribute "%1" is not initialized.</source>
-      <translation>L&apos;attribut &quot;%1&quot; n&apos;est pas initialisé.</translation>
-    </message>
-    <message>
-      <source>Object</source>
-      <translation>Objet</translation>
-    </message>
-    <message>
-      <source>Select external edge or vertex.</source>
-      <translation>Sélectionnez une arête externe ou un sommet.</translation>
-    </message>
-  </context>
-
   <context>
     <name>Sketch</name>
     <message>
       <translation>Inclure la fonctionnalité projetée dans le résultat de l&apos;esquisse</translation>
     </message>
   </context>
+  <context>
+    <name>SketchProjection:keep_reference</name>
+    <message>
+      <source>A sketch entity will be created without connection to the selected shape.</source>
+      <translation>Une entité d&apos;esquisse sera créée sans connexion à la forme sélectionnée.</translation>
+    </message>
+    <message>
+      <source>The reference to the original curve is stored. So it can be changed later.</source>
+      <translation>La référence à la courbe d&apos;origine est stockée. Elle peut donc être modifiée ultérieurement.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:make_fixed</name>
+    <message>
+      <source>Assign the Fixed constraint to the result of projection</source>
+      <translation>Affectez la contrainte Fixe au résultat de la projection</translation>
+    </message>
+    <message>
+      <source>Make projected curve fixed</source>
+      <translation>Fixer la courbe projetée</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:ExternalFeature:SketchPlugin_ProjectionValidator</name>
+    <message>
+      <source>The attribute with the %1 type is not processed</source>
+      <translation>Un argument de type %1 de la fonctionnalité de projection n&apos;est pas pris en charge</translation>
+    </message>
+    <message>
+      <source>The attribute %1 should be an edge</source>
+      <translation>L&apos;attribut %1 doit être une arête</translation>
+    </message>
+    <message>
+      <source>There is no sketch referring to the current feature</source>
+      <translation>La fonction de projection n&apos;a pas d&apos;esquisse</translation>
+    </message>
+    <message>
+      <source>The attribute %1 should be an edge or vertex</source>
+      <translation>L&apos;élément projeté doit être une arête ou un sommet</translation>
+    </message>
+    <message>
+      <source>Unable to project feature from the same sketch</source>
+      <translation>Les fonctions de l&apos;esquisse en cours ne peuvent pas être projetées</translation>
+    </message>
+    <message>
+      <source>Error: Line is orthogonal to the sketch plane.</source>
+      <translation>Erreur: La ligne est orthogonale au plan d&apos;esquisse.</translation>
+    </message>
+    <message>
+      <source>Error: Circle is orthogonal to the sketch plane.</source>
+      <translation>Erreur: Le cercle est orthogonal au plan d&apos;esquisse.</translation>
+    </message>
+    <message>
+      <source>Error: Arc is orthogonal to the sketch plane.</source>
+      <translation>Erreur: L&apos;arc est orthogonal au plan d&apos;esquisse.</translation>
+    </message>
+    <message>
+      <source>Error: Ellipse is orthogonal to the sketch plane.</source>
+      <translation>Erreur: L&apos;ellipse est orthogonale au plan d&apos;esquisse.</translation>
+    </message>
+    <message>
+      <source>Error: Elliptic Arc is orthogonal to the sketch plane.</source>
+      <translation>Erreur: L&apos;arc d&apos;ellipse est orthogonal au plan d&apos;esquisse.</translation>
+    </message>
+    <message>
+      <source>Error: Selected object is not supported for projection.</source>
+      <translation>Erreur: L&apos;objet sélectionné n&apos;est pas pris en charge pour la projection.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:Model_FeatureValidator</name>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>L&apos;attribut &quot;%1&quot; n&apos;est pas initialisé.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchProjection:ExternalFeature</name>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Sélectionnez une arête externe ou un sommet.</translation>
+    </message>
+    <message>
+      <source>Object</source>
+      <translation>Objet</translation>
+    </message>
+    <message>
+      <source>Select external edge or vertex.</source>
+      <translation>Sélectionnez une arête externe ou un sommet.</translation>
+    </message>
+  </context>
 
   <context>
     <name>SketchRectangle</name>
     </message>
   </context>
 
+  <!-- SketchCurveFitting-->
+  <context>
+    <name>SketchCurveFitting</name>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Courbe d&apos;ajustement</translation>
+    </message>
+    <message>
+      <source>Create curve passing through the points</source>
+      <translation>Créer une courbe passant par les points</translation>
+    </message>
+    <message>
+      <source>Error: Feature "%1" does not support action "%2".</source>
+      <translation>Erreur: la fonctionnalité "% 1" ne prend pas en charge l'action "% 2".</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:SketchPlugin_CurveFittingValidator</name>
+    <message>
+      <source>Not enough points selected. Need at least %1 points.</source>
+      <translation>Pas assez de points sélectionnés. Besoin d&apos;au moins %1 points.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:points</name>
+    <message>
+      <source>Points</source>
+      <translation>Points</translation>
+    </message>
+    <message>
+      <source>Select points for curve fitting</source>
+      <translation>Sélectionner des points pour l&apos;ajustement de courbe</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Sélectionner des points pour l'ajustement de courbe</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:periodic</name>
+    <message>
+      <source>Periodic</source>
+      <translation>Périodique</translation>
+    </message>
+    <message>
+      <source>Make curve periodic</source>
+      <translation>Rendre la courbe périodique</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:closed</name>
+    <message>
+      <source>Closed</source>
+      <translation>Fermé</translation>
+    </message>
+    <message>
+      <source>Make curve closed, but not periodic</source>
+      <translation>Rendre la courbe fermée, mais pas périodique</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:type</name>
+    <message>
+      <source>Interpolation</source>
+      <translation>Interpolation</translation>
+    </message>
+    <message>
+      <source>Approximation</source>
+      <translation>Approximation</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:precision</name>
+    <message>
+      <source>Precision</source>
+      <translation>Précision</translation>
+    </message>
+    <message>
+      <source>Maximal distance from selected points to the curve</source>
+      <translation>Distance maximale entre les points sélectionnés et la courbe</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:need_control_poly</name>
+    <message>
+      <source>Create control polygon</source>
+      <translation>Créer un polygone de contrôle</translation>
+    </message>
+    <message>
+      <source>Specify if the control polygon should be created</source>
+      <translation>Précisez si le polygone de contrôle doit être créé</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:reorder_points</name>
+    <message>
+      <source>Reorder points</source>
+      <translation>Réorganiser les points</translation>
+    </message>
+    <message>
+      <source>Sort selected points to minimize the distance heighbors</source>
+      <translation>Trier les points sélectionnés pour minimiser la distance des voisins</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:Auxiliary</name>
+    <message>
+      <source>Auxiliary</source>
+      <translation>Auxiliaire</translation>
+    </message>
+    <message>
+      <source>Construction element</source>
+      <translation>Élément de construction</translation>
+    </message>
+  </context>
+
 </TS>
diff --git a/src/SketchPlugin/Test/TestCurveFitting1.py b/src/SketchPlugin/Test/TestCurveFitting1.py
new file mode 100644 (file)
index 0000000..7c475ef
--- /dev/null
@@ -0,0 +1,168 @@
+# 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
+#
+
+"""
+    Test creation of B-spline curve passing through the list of points
+"""
+
+import math
+import unittest
+
+from salome.shaper import model
+
+from GeomAPI import *
+from ModelAPI import *
+from ModelHighAPI import *
+from SketchAPI import *
+from GeomAlgoAPI import GeomAlgoAPI_ShapeTools as shapetools
+
+__updated__ = "2020-06-27"
+
+TOLERANCE = 1.e-7
+
+class TestInterpolation(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    coordinates = [(50., 50.), (70., 70.), (80., 30.), (50., 10.), (10., -30.)]
+    self.myPoints = []
+    self.myPointRefs = []
+    for p in coordinates:
+      self.myPoints.append(self.mySketch.addPoint(p[0], p[1]))
+      self.myPointRefs.append(self.myPoints[-1].coordinates())
+    self.myDOF = 10
+    self.myNbPoints = len(self.myPoints)
+    self.myNbLines = 0
+    self.myNbSplines = 0
+    self.myNbPeriodicSplines = 0
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+    model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+    model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbSplines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSplinePeriodic", self.myNbPeriodicSplines)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def checkPointsOnCurve(self, theCurve):
+    for p in self.myPoints:
+      shape = p.defaultResult().shape()
+      self.assertTrue(shapetools.minimalDistance(shape, theCurve) < TOLERANCE)
+
+
+  def test_interpolation(self):
+    """ Test 1. Create B-spline curve by set of points
+    """
+    self.mySpline = self.mySketch.addInterpolation(self.myPointRefs)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs) - 2
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 172.237458
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_interpolation_periodic(self):
+    """ Test 2. Create periodic B-spline curve by set of points
+    """
+    self.mySpline = self.mySketch.addInterpolation(self.myPointRefs, periodic = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs)
+    self.myNbPeriodicSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == True)
+    EXPECTED_LEN = 262.60929483
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_interpolation_closed(self):
+    """ Test 3. Create closed but not periodic B-spline curve by set of points
+    """
+    self.mySpline = self.mySketch.addInterpolation(self.myPointRefs, closed = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs) - 3
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    self.assertTrue(curve.poles()[0].distance(curve.poles()[-1]) < TOLERANCE)
+    EXPECTED_LEN = 274.25674928
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_interpolation_reorder(self):
+    """ Test 4. Check reordering of points works properly
+    """
+    model.do()
+    # use low-level API to create feature
+    curveFitting = featureToCompositeFeature(self.mySketch.feature()).addFeature("SketchCurveFitting")
+    curveFitting.string("type").setValue("interpolation_type")
+    curveFitting.boolean("periodic").setValue(False)
+    curveFitting.boolean("closed").setValue(False)
+    pointsAttr = curveFitting.refattrlist("points")
+    for ind in [0, 3, 4, 2, 1]:
+      pointsAttr.append(self.myPointRefs[ind])
+    # perform reordering
+    curveFitting.customAction("reorder_points")
+    model.do()
+
+    self.mySpline = SketchAPI_BSpline(model.lastSubFeature(self.mySketch, "SketchBSpline"))
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs) - 2
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 172.237458
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestCurveFitting2.py b/src/SketchPlugin/Test/TestCurveFitting2.py
new file mode 100644 (file)
index 0000000..c1a108e
--- /dev/null
@@ -0,0 +1,179 @@
+# 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
+#
+
+"""
+    Test creation of B-spline curve passing through the list of points
+"""
+
+import math
+import unittest
+
+from salome.shaper import model
+
+from GeomAPI import *
+from ModelAPI import *
+from ModelHighAPI import *
+from SketchAPI import *
+from GeomAlgoAPI import GeomAlgoAPI_ShapeTools as shapetools
+
+__updated__ = "2020-06-27"
+
+TOLERANCE = 1.e-7
+POINT_NAMES = []
+
+class TestInterpolationExternal(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    self.myPoints = []
+    self.myPointRefs = []
+    for name in POINT_NAMES:
+      proj = self.mySketch.addProjection(model.selection("VERTEX", name), False)
+      self.myPoints.append(SketchAPI_Point(proj.createdFeature()))
+      self.myPointRefs.append(self.myPoints[-1].coordinates())
+    self.myDOF = 0
+    self.myNbPoints = len(self.myPoints)
+    self.myNbLines = 0
+    self.myNbSplines = 0
+    self.myNbPeriodicSplines = 0
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+    model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+    model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbSplines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSplinePeriodic", self.myNbPeriodicSplines)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def checkPointsOnCurve(self, theCurve):
+    for p in self.myPoints:
+      shape = p.defaultResult().shape()
+      self.assertTrue(shapetools.minimalDistance(shape, theCurve) < TOLERANCE)
+
+
+  def test_interpolation(self):
+    """ Test 1. Create B-spline curve by set of points
+    """
+    self.mySpline = self.mySketch.addInterpolation(self.myPointRefs)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs) - 2
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 172.237458
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_interpolation_periodic(self):
+    """ Test 2. Create periodic B-spline curve by set of points
+    """
+    self.mySpline = self.mySketch.addInterpolation(self.myPointRefs, periodic = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs)
+    self.myNbPeriodicSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == True)
+    EXPECTED_LEN = 262.60929483
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_interpolation_closed(self):
+    """ Test 3. Create closed but not periodic B-spline curve by set of points
+    """
+    self.mySpline = self.mySketch.addInterpolation(self.myPointRefs, closed = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs) - 3
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    self.assertTrue(curve.poles()[0].distance(curve.poles()[-1]) < TOLERANCE)
+    EXPECTED_LEN = 274.25674928
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_interpolation_reorder(self):
+    """ Test 4. Check reordering of points works properly
+    """
+    model.do()
+    # use low-level API to create feature
+    curveFitting = featureToCompositeFeature(self.mySketch.feature()).addFeature("SketchCurveFitting")
+    curveFitting.string("type").setValue("interpolation_type")
+    curveFitting.boolean("periodic").setValue(False)
+    curveFitting.boolean("closed").setValue(False)
+    pointsAttr = curveFitting.refattrlist("points")
+    for ind in [0, 3, 4, 2, 1]:
+      pointsAttr.append(self.myPointRefs[ind])
+    # perform reordering
+    curveFitting.customAction("reorder_points")
+    model.do()
+
+    self.mySpline = SketchAPI_BSpline(model.lastSubFeature(self.mySketch, "SketchBSpline"))
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - len(self.myPointRefs) - 2
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 172.237458
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+
+if __name__ == "__main__":
+    # Create a sketch with the external points
+    model.begin()
+    document = model.moduleDocument()
+    coordinates = [(50., 50.), (70., 70.), (80., 30.), (50., 10.), (10., -30.)]
+    for p in coordinates:
+      point3D = model.addPoint(document, p[0], p[1], 0)
+      POINT_NAMES.append(point3D.name())
+    model.end()
+
+    # Run testing
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestCurveFitting3.py b/src/SketchPlugin/Test/TestCurveFitting3.py
new file mode 100644 (file)
index 0000000..7974182
--- /dev/null
@@ -0,0 +1,231 @@
+# 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
+#
+
+"""
+    Test creation of B-spline curve passing through the list of points
+"""
+
+import math
+import unittest
+
+from salome.shaper import model
+
+from GeomAPI import *
+from ModelAPI import *
+from ModelHighAPI import *
+from SketchAPI import *
+from GeomAlgoAPI import GeomAlgoAPI_ShapeTools as shapetools
+
+__updated__ = "2020-06-28"
+
+TOLERANCE = 1.e-7
+
+class TestApproximation(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    coordinates = [(50., 50.), (70., 70.), (80., 30.), (50., 10.), (10., -30.)]
+    self.myPoints = []
+    self.myPointRefs = []
+    for p in coordinates:
+      self.myPoints.append(self.mySketch.addPoint(p[0], p[1]))
+      self.myPointRefs.append(self.myPoints[-1].coordinates())
+    self.myDOF = 10
+    self.myNbPoints = len(self.myPoints)
+    self.myNbLines = 0
+    self.myNbSplines = 0
+    self.myNbPeriodicSplines = 0
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+    model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+    model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbSplines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSplinePeriodic", self.myNbPeriodicSplines)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def checkPointsOnCurve(self, theCurve, theTolerance = 1.e-3):
+    for p in self.myPoints:
+      shape = p.defaultResult().shape()
+      self.assertTrue(shapetools.minimalDistance(shape, theCurve) < theTolerance)
+
+
+  def test_approximation_default(self):
+    """ Test 1. Create B-spline curve approximating a set of points with the default precision
+    """
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 4)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 189.17832318
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_approximation_custom(self):
+    """ Test 2. Create B-spline curve approximating a set of points with the custom precision
+    """
+    PRECISION = 10
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, precision = PRECISION)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 166.97400096
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+  def test_approximation_periodic_default(self):
+    """ Test 3. Create periodic B-spline curve approximating set of points with the default precision
+    """
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, periodic = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2
+    self.myNbPeriodicSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == True)
+    EXPECTED_LEN = 390.98013747
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_approximation_periodic_custom(self):
+    """ Test 4. Create periodic B-spline curve approximating set of points with the custom precision
+    """
+    PRECISION = 10
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, precision = PRECISION, periodic = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2
+    self.myNbPeriodicSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == True)
+    EXPECTED_LEN = 390.98013747
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+  def test_approximation_closed_default(self):
+    """ Test 5. Create closed but not periodic B-spline curve approximating set of points with the default precision
+    """
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, closed = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 5)
+    self.assertTrue(curve.isPeriodic() == False)
+    self.assertTrue(curve.poles()[0].distance(curve.poles()[-1]) < TOLERANCE)
+    EXPECTED_LEN = 413.18489109
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_approximation_closed_custom(self):
+    """ Test 6. Create closed but not periodic B-spline curve approximating set of points with the custom precision
+    """
+    PRECISION = 10
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, precision = PRECISION, closed = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    self.assertTrue(curve.poles()[0].distance(curve.poles()[-1]) < TOLERANCE)
+    EXPECTED_LEN = 276.27724118
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+  def test_approximation_reorder(self):
+    """ Test 7. Check reordering of points works properly
+    """
+    PRECISION = 10
+    model.do()
+    # use low-level API to create feature
+    curveFitting = featureToCompositeFeature(self.mySketch.feature()).addFeature("SketchCurveFitting")
+    curveFitting.string("type").setValue("approximation_type")
+    curveFitting.real("precision").setValue(PRECISION)
+    curveFitting.boolean("periodic").setValue(False)
+    curveFitting.boolean("closed").setValue(False)
+    pointsAttr = curveFitting.refattrlist("points")
+    for ind in [0, 3, 4, 2, 1]:
+      pointsAttr.append(self.myPointRefs[ind])
+    # perform reordering
+    curveFitting.customAction("reorder_points")
+    model.do()
+
+    self.mySpline = SketchAPI_BSpline(model.lastSubFeature(self.mySketch, "SketchBSpline"))
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 166.97400096
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestCurveFitting4.py b/src/SketchPlugin/Test/TestCurveFitting4.py
new file mode 100644 (file)
index 0000000..0b67fdf
--- /dev/null
@@ -0,0 +1,242 @@
+# 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
+#
+
+"""
+    Test creation of B-spline curve passing through the list of points
+"""
+
+import math
+import unittest
+
+from salome.shaper import model
+
+from GeomAPI import *
+from ModelAPI import *
+from ModelHighAPI import *
+from SketchAPI import *
+from GeomAlgoAPI import GeomAlgoAPI_ShapeTools as shapetools
+
+__updated__ = "2020-06-28"
+
+TOLERANCE = 1.e-7
+POINT_NAMES = []
+
+class TestApproximationExternal(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    self.myPoints = []
+    self.myPointRefs = []
+    for name in POINT_NAMES:
+      proj = self.mySketch.addProjection(model.selection("VERTEX", name), False)
+      self.myPoints.append(SketchAPI_Point(proj.createdFeature()))
+      self.myPointRefs.append(self.myPoints[-1].coordinates())
+    self.myDOF = 0
+    self.myNbPoints = len(self.myPoints)
+    self.myNbLines = 0
+    self.myNbSplines = 0
+    self.myNbPeriodicSplines = 0
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+    model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+    model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbSplines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSplinePeriodic", self.myNbPeriodicSplines)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def checkPointsOnCurve(self, theCurve, theTolerance = 1.e-3):
+    for p in self.myPoints:
+      shape = p.defaultResult().shape()
+      self.assertTrue(shapetools.minimalDistance(shape, theCurve) < theTolerance)
+
+
+  def test_approximation_default(self):
+    """ Test 1. Create B-spline curve approximating a set of points with the default precision
+    """
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 4)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 189.17832318
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_approximation_custom(self):
+    """ Test 2. Create B-spline curve approximating a set of points with the custom precision
+    """
+    PRECISION = 10
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, precision = PRECISION)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 166.97400096
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+  def test_approximation_periodic_default(self):
+    """ Test 3. Create periodic B-spline curve approximating set of points with the default precision
+    """
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, periodic = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2
+    self.myNbPeriodicSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == True)
+    EXPECTED_LEN = 390.98013747
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_approximation_periodic_custom(self):
+    """ Test 4. Create periodic B-spline curve approximating set of points with the custom precision
+    """
+    PRECISION = 10
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, precision = PRECISION, periodic = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2
+    self.myNbPeriodicSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == True)
+    EXPECTED_LEN = 390.98013747
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+  def test_approximation_closed_default(self):
+    """ Test 5. Create closed but not periodic B-spline curve approximating set of points with the default precision
+    """
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, closed = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 5)
+    self.assertTrue(curve.isPeriodic() == False)
+    self.assertTrue(curve.poles()[0].distance(curve.poles()[-1]) < TOLERANCE)
+    EXPECTED_LEN = 413.18489109
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge)
+
+  def test_approximation_closed_custom(self):
+    """ Test 6. Create closed but not periodic B-spline curve approximating set of points with the custom precision
+    """
+    PRECISION = 10
+    self.mySpline = self.mySketch.addApproximation(self.myPointRefs, precision = PRECISION, closed = True)
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    self.assertTrue(curve.poles()[0].distance(curve.poles()[-1]) < TOLERANCE)
+    EXPECTED_LEN = 276.27724118
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+  def test_approximation_reorder(self):
+    """ Test 7. Check reordering of points works properly
+    """
+    PRECISION = 10
+    model.do()
+    # use low-level API to create feature
+    curveFitting = featureToCompositeFeature(self.mySketch.feature()).addFeature("SketchCurveFitting")
+    curveFitting.string("type").setValue("approximation_type")
+    curveFitting.real("precision").setValue(PRECISION)
+    curveFitting.boolean("periodic").setValue(False)
+    curveFitting.boolean("closed").setValue(False)
+    pointsAttr = curveFitting.refattrlist("points")
+    for ind in [0, 3, 4, 2, 1]:
+      pointsAttr.append(self.myPointRefs[ind])
+    # perform reordering
+    curveFitting.customAction("reorder_points")
+    model.do()
+
+    self.mySpline = SketchAPI_BSpline(model.lastSubFeature(self.mySketch, "SketchBSpline"))
+    edge = GeomAPI_Edge(self.mySpline.defaultResult().shape())
+    curve = GeomAPI_BSpline(GeomAPI_Curve(edge))
+    self.myDOF += len(curve.poles()) * 2 - 4
+    self.myNbSplines += 1
+    model.do()
+
+    self.assertTrue(self.mySpline.feature())
+    self.assertTrue(self.mySpline.feature().error() == "")
+    self.assertTrue(self.mySpline.degree().value() == 3)
+    self.assertTrue(curve.isPeriodic() == False)
+    EXPECTED_LEN = 166.97400096
+    self.assertTrue(math.fabs(edge.length() - EXPECTED_LEN) < TOLERANCE, "Curve length: {}; expected: {}".format(edge.length(), EXPECTED_LEN))
+    # check points lying of the created curve
+    self.checkPointsOnCurve(edge, PRECISION)
+
+
+if __name__ == "__main__":
+    # Create a sketch with the external points
+    model.begin()
+    document = model.moduleDocument()
+    coordinates = [(50., 50.), (70., 70.), (80., 30.), (50., 10.), (10., -30.)]
+    for p in coordinates:
+      point3D = model.addPoint(document, p[0], p[1], 0)
+      POINT_NAMES.append(point3D.name())
+    model.end()
+
+    # Run testing
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestOffset1.py b/src/SketchPlugin/Test/TestOffset1.py
new file mode 100644 (file)
index 0000000..24b1622
--- /dev/null
@@ -0,0 +1,222 @@
+# 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
+#
+
+"""
+    TestOffset.py
+    Unit test of SketchPlugin_Offset class
+
+    SketchPlugin_Offset
+        static const std::string ID("SketchOffset");
+        data()->addAttribute(EDGES_ID(), ModelAPI_AttributeRefList::typeId());
+        data()->addAttribute(VALUE_ID(), ModelAPI_AttributeDouble::typeId());
+        data()->addAttribute(REVERSED_ID(), ModelAPI_AttributeBoolean::typeId());
+        data()->addAttribute(SketchPlugin_Constraint::ENTITY_A(), ModelAPI_AttributeRefList::typeId());
+        data()->addAttribute(SketchPlugin_Constraint::ENTITY_B(), ModelAPI_AttributeRefList::typeId());
+        data()->addAttribute(SketchPlugin_Constraint::ENTITY_C(), ModelAPI_AttributeIntArray::typeId());
+"""
+
+from GeomDataAPI import *
+from ModelAPI import *
+import math
+from salome.shaper import model
+
+#=========================================================================
+# Initialization of the test
+#=========================================================================
+
+__updated__ = "2020-06-30"
+
+#=========================================================================
+# Auxiliary functions
+#=========================================================================
+def normalize(theDir):
+    aLen = math.hypot(theDir[0], theDir[1])
+    if aLen < 1.e-10:
+        aLen = 1.0
+    return [theDir[0] / aLen, theDir[1] / aLen]
+
+def checkOffset(theListIn, theListOut, theOutToIn, theDist, isReversed, nbIn, nbOut):
+    TOL = 6.e-5
+    aNbIn  = theListIn.size()
+    aNbOut = theListOut.size()
+
+    #print("**checkOffset**")
+    assert (theListIn.size() == nbIn)
+    assert (theListOut.size() == nbOut)
+    assert (theOutToIn.size() == nbOut)
+
+    for ind in range(0, aNbOut):
+        aFeatureOut = ModelAPI_Feature.feature(theListOut.object(ind))
+        assert(aFeatureOut is not None)
+        anInInd = theOutToIn.value(ind)
+        if (anInInd == -1):
+            assert(aFeatureOut.getKind() == "SketchArc")
+        else:
+            aFeatureIn = ModelAPI_Feature.feature(theListIn.object(anInInd))
+            assert(aFeatureIn is not None)
+
+            #print(aFeatureIn.getKind())
+            if (aFeatureIn.getKind() == "SketchLine"):
+                assert(aFeatureOut.getKind() == aFeatureIn.getKind())
+                # Line and its offset are parallel
+                aP1Out = geomDataAPI_Point2D(aFeatureOut.attribute('StartPoint'))
+                aP2Out = geomDataAPI_Point2D(aFeatureOut.attribute('EndPoint'))
+                aP1In  = geomDataAPI_Point2D(aFeatureIn.attribute('StartPoint'))
+                aP2In  = geomDataAPI_Point2D(aFeatureIn.attribute('EndPoint'))
+                aDirOut = [aP2Out.x() - aP1Out.x(), aP2Out.y() - aP1Out.y()]
+                aDirIn  = [ aP2In.x() -  aP1In.x(),  aP2In.y() -  aP1In.y()]
+                aCross = aDirOut[0] * aDirIn[1] - aDirOut[1] * aDirIn[0]
+                assert math.fabs(aCross) < TOL, "aCross = {0}".format(aCross)
+            elif (aFeatureIn.getKind() == "SketchArc"):
+                assert(aFeatureOut.getKind() == aFeatureIn.getKind())
+                # Arc and its offset have the same center
+                aCPOut = geomDataAPI_Point2D(aFeatureOut.attribute('center_point'))
+                aCPIn  = geomDataAPI_Point2D(aFeatureIn.attribute('center_point'))
+                assert (math.fabs(aCPOut.x() - aCPIn.x()) < TOL)
+                assert (math.fabs(aCPOut.y() - aCPIn.y()) < TOL)
+
+
+#=========================================================================
+# Start of test
+#=========================================================================
+aSession = ModelAPI_Session.get()
+aDocument = aSession.moduleDocument()
+#=========================================================================
+# Creation of a sketch
+#=========================================================================
+aSession.startOperation()
+aSketchCommonFeature = aDocument.addFeature("Sketch")
+aSketchFeature = featureToCompositeFeature(aSketchCommonFeature)
+origin = geomDataAPI_Point(aSketchFeature.attribute("Origin"))
+origin.setValue(0, 0, 0)
+dirx = geomDataAPI_Dir(aSketchFeature.attribute("DirX"))
+dirx.setValue(1, 0, 0)
+norm = geomDataAPI_Dir(aSketchFeature.attribute("Norm"))
+norm.setValue(0, 0, 1)
+aSession.finishOperation()
+#=========================================================================
+# Creation of an arc and two lines
+#=========================================================================
+# Arc
+aSession.startOperation()
+aSketchArc1 = aSketchFeature.addFeature("SketchArc")
+anArcCentr = geomDataAPI_Point2D(aSketchArc1.attribute("center_point"))
+anArcCentr.setValue(10., 10.)
+anArcStartPoint = geomDataAPI_Point2D(aSketchArc1.attribute("start_point"))
+anArcStartPoint.setValue(50., 0.)
+anArcEndPoint = geomDataAPI_Point2D(aSketchArc1.attribute("end_point"))
+anArcEndPoint.setValue(0., 50.)
+aSession.finishOperation()
+# Line 1
+aSession.startOperation()
+aSketchLine1 = aSketchFeature.addFeature("SketchLine")
+aLine1StartPoint = geomDataAPI_Point2D(aSketchLine1.attribute("StartPoint"))
+aLine1EndPoint = geomDataAPI_Point2D(aSketchLine1.attribute("EndPoint"))
+aLine1StartPoint.setValue(0., 50.)
+aLine1EndPoint.setValue(-20., 0.)
+aSession.finishOperation()
+# Line 2
+aSession.startOperation()
+aSketchLine2 = aSketchFeature.addFeature("SketchLine")
+aLine2StartPoint = geomDataAPI_Point2D(aSketchLine2.attribute("StartPoint"))
+aLine2EndPoint = geomDataAPI_Point2D(aSketchLine2.attribute("EndPoint"))
+aLine2StartPoint.setValue(50., 0.)
+aLine2EndPoint.setValue(-20., 0.)
+aSession.finishOperation()
+assert (model.dof(aSketchFeature) == 13)
+#=========================================================================
+# Link arc points and lines points by the coincidence constraints
+#=========================================================================
+aSession.startOperation()
+aConstraint = aSketchFeature.addFeature("SketchConstraintCoincidence")
+reflistA = aConstraint.refattr("ConstraintEntityA")
+reflistB = aConstraint.refattr("ConstraintEntityB")
+reflistA.setAttr(anArcEndPoint)
+reflistB.setAttr(aLine1StartPoint)
+aConstraint.execute()
+aSession.finishOperation()
+aSession.startOperation()
+aConstraint = aSketchFeature.addFeature("SketchConstraintCoincidence")
+reflistA = aConstraint.refattr("ConstraintEntityA")
+reflistB = aConstraint.refattr("ConstraintEntityB")
+reflistA.setAttr(anArcStartPoint)
+reflistB.setAttr(aLine2StartPoint)
+aConstraint.execute()
+aSession.finishOperation()
+aSession.startOperation()
+aConstraint = aSketchFeature.addFeature("SketchConstraintCoincidence")
+reflistA = aConstraint.refattr("ConstraintEntityA")
+reflistB = aConstraint.refattr("ConstraintEntityB")
+reflistA.setAttr(aLine1EndPoint)
+reflistB.setAttr(aLine2EndPoint)
+aConstraint.execute()
+aSession.finishOperation()
+assert (model.dof(aSketchFeature) == 7)
+#=========================================================================
+# Make offset for objects created above
+#=========================================================================
+VALUE = 13
+IS_REVERSED = False
+aSession.startOperation()
+anOffset = aSketchFeature.addFeature("SketchOffset")
+aRefListInitial = anOffset.reflist("segments")
+aRefListInitial.append(aSketchLine1.lastResult())
+aRefListInitial.append(aSketchArc1.lastResult())
+aRefListInitial.append(aSketchLine2.lastResult())
+anOffset.real("offset_value").setValue(VALUE)
+anOffset.boolean("reversed").setValue(IS_REVERSED)
+anOffset.execute()
+aSession.finishOperation()
+assert (model.dof(aSketchFeature) == 7)
+#=========================================================================
+# Verify all offset objects
+#=========================================================================
+aRefListA = anOffset.reflist("ConstraintEntityA")
+aRefListB = anOffset.reflist("ConstraintEntityB")
+anOffsetToBaseMap = anOffset.intArray("ConstraintEntityC")
+checkOffset(aRefListA, aRefListB, anOffsetToBaseMap, VALUE, IS_REVERSED, 3, 6)
+assert (model.dof(aSketchFeature) == 7)
+
+#=========================================================================
+# Remove object from offset
+#=========================================================================
+aSession.startOperation()
+aRefListInitial.remove(aSketchLine2.lastResult())
+aSession.finishOperation()
+checkOffset(aRefListA, aRefListB, anOffsetToBaseMap, VALUE, IS_REVERSED, 2, 4)
+assert (model.dof(aSketchFeature) == 7)
+
+#=========================================================================
+# Clear list of objects
+#=========================================================================
+aSession.startOperation()
+aRefListInitial.clear()
+#TODO: uncomment next line
+#checkOffset(aRefListA, aRefListB, anOffsetToBaseMap, VALUE, IS_REVERSED, 0, 0)
+# add arc once again
+aRefListInitial.append(aSketchArc1.lastResult())
+aSession.finishOperation()
+checkOffset(aRefListA, aRefListB, anOffsetToBaseMap, VALUE, IS_REVERSED, 1, 1)
+assert (model.dof(aSketchFeature) == 7)
+
+#=========================================================================
+# End of test
+#=========================================================================
+
+assert(model.checkPythonDump())
diff --git a/src/SketchPlugin/Test/TestOffset2.py b/src/SketchPlugin/Test/TestOffset2.py
new file mode 100644 (file)
index 0000000..e15e35d
--- /dev/null
@@ -0,0 +1,61 @@
+# 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
+
+SKETCH_DOF = 9
+
+model.begin()
+partSet = model.moduleDocument()
+
+### Create Sketch
+Sketch_1 = model.addSketch(partSet, model.defaultPlane("XOY"))
+
+### Create SketchArc
+SketchArc_1 = Sketch_1.addArc(10, 10, 50, 0, 0, 50, False)
+
+### Create SketchLine
+SketchLine_1 = Sketch_1.addLine(0, 50, -20, 0)
+
+### Create SketchLine
+SketchLine_2 = Sketch_1.addLine(50, 0, -20, 0)
+Sketch_1.setCoincident(SketchArc_1.endPoint(), SketchLine_1.startPoint())
+Sketch_1.setCoincident(SketchArc_1.startPoint(), SketchLine_2.startPoint())
+model.do()
+
+assert(model.dof(Sketch_1) == SKETCH_DOF)
+
+### Create SketchOffset
+SketchOffset_1_objects = [SketchLine_1.result(), SketchArc_1.results()[1], SketchLine_2.result()]
+SketchOffset_1 = Sketch_1.addOffset(SketchOffset_1_objects, 13, False)
+model.do()
+
+# DoF should not change
+assert(model.dof(Sketch_1) == SKETCH_DOF)
+# check number of features
+assert(len(SketchOffset_1.offset()) == 5)
+model.testNbSubFeatures(Sketch_1, "SketchPoint", 0)
+model.testNbSubFeatures(Sketch_1, "SketchLine", 4)
+model.testNbSubFeatures(Sketch_1, "SketchArc", 4)
+model.testNbSubFeatures(Sketch_1, "SketchBSpline", 0)
+model.testNbSubFeatures(Sketch_1, "SketchBSplinePeriodic", 0)
+
+model.end()
+
+assert(model.checkPythonDump())
index 713d690f2de3ec000dcf3548e1d72956c3475fa0..8d1985613c5be54d38da9dce95734c45b7342b52 100644 (file)
@@ -49,12 +49,11 @@ model.end()
 
 from GeomAPI import *
 
+circle = SketchCircle_2.results()[-1].resultSubShapePair()[0].shape()
+assert(circle.isEdge() and circle.edge().isCircle())
 ellipse1 = SketchEllipse_1.results()[-1].resultSubShapePair()[0].shape()
 assert(ellipse1.isEdge() and ellipse1.edge().isEllipse())
 ellipse2 = SketchEllipse_2.results()[-1].resultSubShapePair()[0].shape()
 assert(ellipse2.isEdge() and ellipse2.edge().isEllipse())
 
-# TODO [limitation]: projection of an ellipse to non-parallel plane is forbiden (OCCT issue #31016)
-assert(Sketch_2.feature().error() != "")
-
 assert(model.checkPythonDump())
diff --git a/src/SketchPlugin/Test/TestProjectionWithoutReference.py b/src/SketchPlugin/Test/TestProjectionWithoutReference.py
new file mode 100644 (file)
index 0000000..82e5f54
--- /dev/null
@@ -0,0 +1,146 @@
+# 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
+#
+
+"""
+    Test projection without the reference to the source geometry.
+"""
+
+import unittest
+
+from salome.shaper import model
+from GeomAPI import *
+from ModelAPI import *
+
+__updated__ = "2020-07-07"
+
+CURVES = []
+PLANE = None
+
+class TestProjectionWithoutRef(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(partSet, model.selection("FACE", PLANE.name()))
+    self.myDOF = 0
+    self.myNbPoints = 1
+    self.myNbLines = 1
+    self.myNbCircles = 0
+    self.myNbArcs = 0
+    self.myNbEllipses = 2
+    self.myNbEllipticArcs = 2
+    self.myNbSplines = 1
+    self.myNbPeriodicSplines = 1
+    self.myNbProjections = 0
+    self.myNbFixedConstraints = 0
+    self.myNbEdgesInSketch = 0
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+    model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+    model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+    model.testNbSubFeatures(self.mySketch, "SketchCircle", self.myNbCircles)
+    model.testNbSubFeatures(self.mySketch, "SketchArc", self.myNbArcs)
+    model.testNbSubFeatures(self.mySketch, "SketchEllipse", self.myNbEllipses)
+    model.testNbSubFeatures(self.mySketch, "SketchEllipticArc", self.myNbEllipticArcs)
+    model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbSplines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSplinePeriodic", self.myNbPeriodicSplines)
+    model.testNbSubFeatures(self.mySketch, "SketchProjection", self.myNbProjections)
+    model.testNbSubFeatures(self.mySketch, "SketchConstraintRigid", self.myNbFixedConstraints)
+    nbEdges = 0
+    exp = GeomAPI_ShapeExplorer(self.mySketch.defaultResult().shape(), GeomAPI_Shape.EDGE)
+    while exp.more(): nbEdges += 1; exp.next()
+    self.assertEqual(self.myNbEdgesInSketch, nbEdges)
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+
+  def test_projection_withref_includeintoresult(self):
+    """ Test 1. Projection with the reference to the original shapes. Projected curves are composed into the sketch result.
+    """
+    for c in CURVES:
+      self.mySketch.addProjection(c, keepResult = True)
+    self.myNbProjections = len(CURVES)
+    self.myNbEdgesInSketch = len(CURVES) - 1
+
+  def test_projection_withref_notincludeintoresult(self):
+    """ Test 2. Projection with the reference to the original shapes. Projected curves are NOT included into the sketch result.
+    """
+    for c in CURVES:
+      self.mySketch.addProjection(c, keepResult = False)
+    self.myNbProjections = len(CURVES)
+
+  def test_projection_withoutref_noconstraints(self):
+    """ Test 3. Projection without the reference to the original shapes. No additional constraints applied.
+    """
+    for c in CURVES:
+      self.mySketch.addProjection(c, keepRefToOriginal = False)
+    model.do()
+    self.myNbEdgesInSketch = len(CURVES) - 1
+    self.myDOF += 2 + 4 + 5 + 7 + 5 + 7 + 6 * 2 + 6 * 2
+
+  def test_projection_withoutref_fixed(self):
+    """ Test 4. Projection without the reference to the original shapes. Additionally, Fixed constraints applied.
+    """
+    model.end()
+    # use the low-level API to access the necessary attributes
+    session = ModelAPI_Session.get()
+    for c in CURVES:
+      session.startOperation()
+      proj = featureToCompositeFeature(self.mySketch.feature()).addFeature("SketchProjection")
+      proj.boolean("IncludeToResult").setValue(False)
+      proj.string("keep_reference").setValue("False")
+      proj.boolean("make_fixed").setValue(True)
+      c.fillAttribute(proj.selection("ExternalFeature"))
+      session.finishOperation()
+    self.myNbEdgesInSketch = len(CURVES) - 1
+    self.myNbFixedConstraints = len(CURVES)
+    model.begin()
+
+
+if __name__ == "__main__":
+    model.begin()
+    partSet = model.moduleDocument()
+    Sketch_1 = model.addSketch(partSet, model.defaultPlane("XOY"))
+    SketchPoint_1 = Sketch_1.addPoint(35, -40)
+    CURVES.append(model.selection("VERTEX", Sketch_1.name() + "/" + SketchPoint_1.name()))
+    SketchLine_1 = Sketch_1.addLine(20, -15, 40, 15)
+    CURVES.append(model.selection("EDGE", Sketch_1.name() + "/" + SketchLine_1.name()))
+    SketchCircle_1 = Sketch_1.addCircle(65, -30, 20)
+    CURVES.append(model.selection("EDGE", Sketch_1.name() + "/" + SketchCircle_1.defaultResult().data().name()))
+    SketchArc_1 = Sketch_1.addArc(60, 15, 80, 0, 50, 33, False)
+    CURVES.append(model.selection("EDGE", Sketch_1.name() + "/" + SketchArc_1.defaultResult().data().name()))
+    SketchEllipse_1 = Sketch_1.addEllipse(25, 30, 40, 30, 10)
+    CURVES.append(model.selection("EDGE", Sketch_1.name() + "/" + SketchEllipse_1.defaultResult().data().name()))
+    SketchEllipticArc_1 = Sketch_1.addEllipticArc(40, 70, 55, 70, 45, 50, 25, 56, False)
+    CURVES.append(model.selection("EDGE", Sketch_1.name() + "/" + SketchEllipticArc_1.defaultResult().data().name()))
+    SketchBSpline_1_poles = [(95, -50), (130, -10), (100, 10), (125, 45), (90, 70), (55, 45)]
+    SketchBSpline_1 = Sketch_1.addSpline(poles = SketchBSpline_1_poles)
+    CURVES.append(model.selection("EDGE", Sketch_1.name() + "/" + SketchBSpline_1.name()))
+    SketchBSplinePeriodic_1_poles = [(95, 80), (135, 90), (145, 55), (130, 30), (125, 70), (105, 60)]
+    SketchBSplinePeriodic_1 = Sketch_1.addSpline(poles = SketchBSplinePeriodic_1_poles, periodic = True)
+    CURVES.append(model.selection("EDGE", Sketch_1.name() + "/" + SketchBSplinePeriodic_1.name()))
+    model.do()
+    PLANE = model.addPlane(partSet, model.selection("FACE", "XOY"), model.selection("EDGE", "OY"), 45)
+    model.end()
+
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
index af4cefe57be960111ca2cb96c8aedc0e0376e77b..00eb370da8d040ef0fb6f896068a43efeea41cb3 100644 (file)
@@ -177,5 +177,6 @@ The plug-in includes the following operations:
    mirrorFeature.rst
    translationFeature.rst
    rotationFeature.rst
+   offsetFeature.rst
    sketchDrawer.rst
    sketchCopy.rst
diff --git a/src/SketchPlugin/doc/TUI_curvefitting.rst b/src/SketchPlugin/doc/TUI_curvefitting.rst
new file mode 100644 (file)
index 0000000..1c71914
--- /dev/null
@@ -0,0 +1,23 @@
+
+  .. _tui_create_interpolation:
+
+Create interpolation curve
+==========================
+
+.. literalinclude:: examples/interpolation.py
+    :linenos:
+    :language: python
+
+:download:`Download this script <examples/interpolation.py>`
+
+
+  .. _tui_create_approximation:
+
+Create approximation curve
+==========================
+
+.. literalinclude:: examples/approximation.py
+    :linenos:
+    :language: python
+
+:download:`Download this script <examples/approximation.py>`
diff --git a/src/SketchPlugin/doc/TUI_offset.rst b/src/SketchPlugin/doc/TUI_offset.rst
new file mode 100644 (file)
index 0000000..6175182
--- /dev/null
@@ -0,0 +1,11 @@
+
+  .. _tui_create_offset:
+
+Create Offset
+=============
+
+.. literalinclude:: examples/offset.py
+    :linenos:
+    :language: python
+
+:download:`Download this script <examples/offset.py>`
diff --git a/src/SketchPlugin/doc/curveFittingFeature.rst b/src/SketchPlugin/doc/curveFittingFeature.rst
new file mode 100644 (file)
index 0000000..1137611
--- /dev/null
@@ -0,0 +1,89 @@
+.. |curvefitting.icon|    image:: images/curvefitting.png
+
+Curve Fitting
+=============
+
+The Curve Fitting is a tool to create a curve by a set of given points.
+
+To start this operation:
+
+#. select *Sketch - > Curve fitting* item in the Main Menu or
+#. click |curvefitting.icon| **Curve fitting** button in the Sketch toolbar:
+
+There are 2 algorithms for the curve creation:
+
+- **Interpolation** - the curve passes exactly through the selected points;
+- **Approximation** - curve passes near the selected points to some extent.
+
+Both algorithms have additional options:
+
+- **Periodic** - forces the created curve to be smooth periodic curve;
+- **Closed** - produces closed, but non-periodic curve. As a result, it has the same start and end points, but it may be connected non-smoothly there;
+- **Create control polygon** - if the fitting curve is a B-spline curve, this option will create it's control polygon.
+
+
+Interpolation
+"""""""""""""
+
+.. image:: images/curvefitting_panel_interpolation.png
+   :align: center
+
+Select the list of points to create a curve. The curve will pass directly through these points in the given order.
+
+Clicking the **Reorder points** button will change the order of selected points to minimize the distances between the neighbors.
+
+**TUI Command**:
+
+.. py:function:: Sketch_1.addInterpolation(points, periodic = False, closed = False)
+
+    :param list: points for the curve.
+    :param boolean: flag to make the curve periodic.
+    :param boolean: flag to make the curve closed but not periodic.
+    :return: Created B-spline curve.
+
+Result
+""""""
+
+The created B-spline curve appears in the view.
+
+.. image:: images/curvefitting_interpolation_res.png
+          :align: center
+
+.. centered::
+   Interpolation curve (non-closed, periodic and closed)
+
+**See Also** a sample TUI Script of :ref:`tui_create_interpolation` operation.
+
+
+Approximation
+"""""""""""""
+
+.. image:: images/curvefitting_panel_approximation.png
+   :align: center
+
+Select the list of points to create a curve and set the **Precision** value. The curve will pass not far than the precision of these points.
+
+Clicking the **Reorder points** button will change the order of selected points to minimize the distances between the neighbors.
+
+**TUI Command**:
+
+.. py:function:: Sketch_1.addApproximation(points, precision = 0.001, periodic = False, closed = False)
+
+    :param list: points for the curve.
+    :param double: how close the curve should pass according to the points.
+    :param boolean: flag to make the curve periodic.
+    :param boolean: flag to make the curve closed but not periodic.
+    :return: Created B-spline curve.
+
+Result
+""""""
+
+The created B-spline curve appears in the view.
+
+.. image:: images/curvefitting_approximation_res.png
+          :align: center
+
+.. centered::
+   Approximated curve (non-closed, periodic and closed)
+
+**See Also** a sample TUI Script of :ref:`tui_create_approximation` operation.
diff --git a/src/SketchPlugin/doc/examples/approximation.py b/src/SketchPlugin/doc/examples/approximation.py
new file mode 100644 (file)
index 0000000..f272558
--- /dev/null
@@ -0,0 +1,38 @@
+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 SketchPoints
+SketchPoint_1 = Sketch_1.addPoint(-70.28350515463917, 1.388316151202744)
+SketchPoint_2 = Sketch_1.addPoint(-26.89862542955327, 51.7147766323024)
+SketchPoint_3 = Sketch_1.addPoint(40.08762886597938, 32.27835051546391)
+SketchPoint_4 = Sketch_1.addPoint(66.46563573883162, -29.8487972508591)
+
+### Create approximating curve
+ApproximationPoints = [SketchPoint_1.coordinates(),
+                       SketchPoint_2.coordinates(),
+                       SketchPoint_3.coordinates(),
+                       SketchPoint_4.coordinates()]
+SketchBSpline_1 = Sketch_1.addApproximation(ApproximationPoints, precision = 30)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_1.startPoint())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_1.endPoint())
+
+### Create periodic approximating curve
+SketchBSpline_2 = Sketch_1.addApproximation(ApproximationPoints, precision = 30, periodic = True)
+
+### Create closed approximating curve
+SketchBSpline_3 = Sketch_1.addApproximation(ApproximationPoints, precision = 30, closed = True)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.startPoint())
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.endPoint())
+
+model.do()
+
+model.end()
diff --git a/src/SketchPlugin/doc/examples/interpolation.py b/src/SketchPlugin/doc/examples/interpolation.py
new file mode 100644 (file)
index 0000000..fc32ac4
--- /dev/null
@@ -0,0 +1,47 @@
+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 SketchPoints
+SketchPoint_1 = Sketch_1.addPoint(-70.28350515463917, 1.388316151202744)
+SketchPoint_2 = Sketch_1.addPoint(-26.89862542955327, 51.7147766323024)
+SketchPoint_3 = Sketch_1.addPoint(40.08762886597938, 32.27835051546391)
+SketchPoint_4 = Sketch_1.addPoint(66.46563573883162, -29.8487972508591)
+
+### Create interpolation curve
+InterpolationPoints = [SketchPoint_1.coordinates(),
+                       SketchPoint_2.coordinates(),
+                       SketchPoint_3.coordinates(),
+                       SketchPoint_4.coordinates()]
+SketchBSpline_1 = Sketch_1.addInterpolation(InterpolationPoints)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_1.startPoint())
+Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_1.result())
+Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_1.result())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_1.endPoint())
+
+### Create periodic interpolation curve
+SketchBSpline_2 = Sketch_1.addInterpolation(InterpolationPoints, periodic = True)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_2.result())
+Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_2.result())
+Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_2.result())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_2.result())
+
+### Create closed interpolation curve
+SketchBSpline_3 = Sketch_1.addInterpolation(InterpolationPoints, closed = True)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.startPoint())
+Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_3.result())
+Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_3.result())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_3.result())
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.endPoint())
+
+model.do()
+
+model.end()
diff --git a/src/SketchPlugin/doc/examples/offset.py b/src/SketchPlugin/doc/examples/offset.py
new file mode 100644 (file)
index 0000000..881db9a
--- /dev/null
@@ -0,0 +1,22 @@
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+
+Sketch_1 = model.addSketch(partSet, model.defaultPlane("XOY"))
+
+SketchLine_1 = Sketch_1.addLine(0, 0, 0, 100)
+SketchLine_2 = Sketch_1.addLine(0, 100, 100, 100)
+SketchLine_3 = Sketch_1.addLine(100, 100, 100, 0)
+SketchLine_4 = Sketch_1.addLine(0, 0, 100, 0)
+
+Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint())
+Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchLine_3.startPoint())
+Sketch_1.setCoincident(SketchLine_3.endPoint(), SketchLine_4.endPoint())
+Sketch_1.setCoincident(SketchLine_1.startPoint(), SketchLine_4.startPoint())
+
+SketchOffset_1_objects = [SketchLine_1.result(), SketchLine_2.result(), SketchLine_3.result(), SketchLine_4.result()]
+SketchOffset_1 = Sketch_1.addOffset(SketchOffset_1_objects, 10.0, False)
+
+model.do()
+model.end()
diff --git a/src/SketchPlugin/doc/images/Offset_panel.png b/src/SketchPlugin/doc/images/Offset_panel.png
new file mode 100644 (file)
index 0000000..d34ad6c
Binary files /dev/null and b/src/SketchPlugin/doc/images/Offset_panel.png differ
diff --git a/src/SketchPlugin/doc/images/Offset_res.png b/src/SketchPlugin/doc/images/Offset_res.png
new file mode 100644 (file)
index 0000000..701faf0
Binary files /dev/null and b/src/SketchPlugin/doc/images/Offset_res.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting.png b/src/SketchPlugin/doc/images/curvefitting.png
new file mode 100644 (file)
index 0000000..dea1f22
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_approximation_res.png b/src/SketchPlugin/doc/images/curvefitting_approximation_res.png
new file mode 100644 (file)
index 0000000..007db89
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_approximation_res.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_interpolation_res.png b/src/SketchPlugin/doc/images/curvefitting_interpolation_res.png
new file mode 100644 (file)
index 0000000..dcde3f0
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_interpolation_res.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_panel_approximation.png b/src/SketchPlugin/doc/images/curvefitting_panel_approximation.png
new file mode 100644 (file)
index 0000000..2593baf
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_panel_approximation.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png b/src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png
new file mode 100644 (file)
index 0000000..29cf328
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png differ
diff --git a/src/SketchPlugin/doc/images/offset.png b/src/SketchPlugin/doc/images/offset.png
new file mode 100644 (file)
index 0000000..8ce6065
Binary files /dev/null and b/src/SketchPlugin/doc/images/offset.png differ
diff --git a/src/SketchPlugin/doc/offsetFeature.rst b/src/SketchPlugin/doc/offsetFeature.rst
new file mode 100644 (file)
index 0000000..ce42254
--- /dev/null
@@ -0,0 +1,59 @@
+.. |offset.icon|    image:: images/offset.png
+
+Offset
+======
+
+Offset operation offsets sketch entities on a given distance.
+Gaps are filled by arcs.
+Offset is performed outside a closed contour or to the right
+of an open one, unless the **Reversed** flag is not set.
+
+To create an Offset in the active Sketch:
+
+#. select in the Main Menu *Sketch - > Offset* item  or
+#. click |offset.icon| **Offset** button in Sketch toolbar:
+
+Property panel:
+
+.. image:: images/Offset_panel.png
+  :align: center
+
+.. centered::
+   Offset
+
+Input fields:
+
+- **Edges** is the list of segments (lines, circles, arcs) selected in the view.
+- **Offset value** is the offset distance.
+- **Reversed** sets the reversed offset side (inside a closed contour or to the left of an open one).
+
+Button:
+
+- **Select wire** button adds edges connected by coincident boundary constraint
+                  and composing a wire with the already selected segments.
+                  Not more than 2 edges can be connected with one coincident point.
+
+**TUI Command**:
+
+.. py:function:: Sketch_1.addOffset(Objects, Distance, isReversed)
+
+    :param list: A list of objects.
+    :param real: An offset distance.
+    :param boolean: Reversed flag.
+    :return: Result object.
+
+Result
+""""""
+
+Created Offset appears in the view.
+
+| The original and the offset objects are marked with a special sign.
+| Offset object is drawn with a thinner line.
+
+.. image:: images/Offset_res.png
+          :align: center
+
+.. centered::
+   Offset created
+
+**See Also** a sample TUI Script of :ref:`tui_create_offset` operation.
diff --git a/src/SketchPlugin/icons/curvefitting.png b/src/SketchPlugin/icons/curvefitting.png
new file mode 100644 (file)
index 0000000..dea1f22
Binary files /dev/null and b/src/SketchPlugin/icons/curvefitting.png differ
diff --git a/src/SketchPlugin/icons/offset.png b/src/SketchPlugin/icons/offset.png
new file mode 100644 (file)
index 0000000..8ce6065
Binary files /dev/null and b/src/SketchPlugin/icons/offset.png differ
index fbf9b0bf87be83c2a72aa9a5e3d449853c76f44a..824e6281b2c9b60f4145dc74efe6a0fd50fbb68b 100644 (file)
@@ -9,6 +9,7 @@
                 SketchBSpline SketchMacroBSpline SketchMacroBSplinePeriodic SketchBSplinePeriodic
                 SketchRectangle
                 SketchProjection
+                SketchCurveFitting
                 SketchConstraintLength SketchConstraintRadius SketchConstraintDistance SketchConstraintDistanceHorizontal SketchConstraintDistanceVertical
                 SketchConstraintParallel SketchConstraintPerpendicular
                 SketchConstraintRigid SketchConstraintHorizontal SketchConstraintVertical
@@ -17,6 +18,7 @@
                 SketchConstraintCoincidence SketchConstraintCoincidenceInternal
                 SketchConstraintMirror SketchConstraintAngle
                 SketchMultiRotation SketchMultiTranslation
+                SketchOffset
                 SketchConstraintCollinear SketchConstraintMiddle"
         when_nested="accept abort"
         title="Sketch"
                    obligatory="0"
                    change_visual_attributes="true"/>
       </feature>
+
+      <!-- Curve fitting -->
+      <feature id="SketchCurveFitting"
+               title="Curve fitting"
+               tooltip="Create curve passing through the points"
+               icon="icons/Sketch/curvefitting.png"
+               helpfile="curveFittingFeature.html">
+        <sketch_multi_selector id="points"
+                               label="Points"
+                               tooltip="Select points for curve fitting"
+                               shape_types="Vertices"
+                               use_external="true"
+                               greed="true">
+        </sketch_multi_selector>
+        <switch id="type">
+          <case id="interpolation_type" title="Interpolation"/>
+          <case id="approximation_type" title="Approximation">
+            <doublevalue id="precision"
+                         label="Precision"
+                         tooltip="Maximal distance from selected points to the curve"
+                         default="0.001"
+                         min="1.e-7"
+                         use_reset="false">
+              <validator id="GeomValidators_Positive" parameters="0"/>
+            </doublevalue>
+          </case>
+        </switch>
+        <boolvalue id="need_control_poly"
+                   label="Create control polygon"
+                   default="true"
+                   tooltip="Specify if the control polygon should be created"/>
+        <optionalbox id="periodic"
+                     title="Periodic"
+                     tooltip="Make curve periodic"
+                     default="false"
+                     has_frame="false"
+                     enable_on_check="false"
+                     show_title="true">
+          <boolvalue id="closed"
+                     label="Closed"
+                     default="false"
+                     tooltip="Make curve closed, but not periodic"
+                     obligatory="1"/>
+        </optionalbox>
+        <boolvalue id="Auxiliary"
+                   label="Auxiliary"
+                   default="false"
+                   tooltip="Construction element"
+                   obligatory="0"
+                   change_visual_attributes="true"/>
+        <validator id="SketchPlugin_CurveFittingValidator"/>
+        <action id="reorder_points"
+                label="Reorder points"
+                tooltip="Sort selected points to minimize the distance heighbors"/>
+      </feature>
     </group>
 
     <group id="Segmentation">
               use_sketch_plane="false">
           <validator id="SketchPlugin_ProjectionValidator"/>
         </sketch_shape_selector>
-        <boolvalue id="IncludeToResult" label="Include into the sketch result" default="true" tooltip="Include projected feature into the sketch result"
-                   change_visual_attributes="true"/>
+        <radiobox id="keep_reference"
+                  align_subs="vertical">
+          <radio id="true"
+                 title="Keep reference to the original shape"
+                 tooltip="The reference to the original curve is stored. So it can be changed later.">
+            <boolvalue id="IncludeToResult"
+                       label="Include into the sketch result"
+                       default="true"
+                       tooltip="Include projected feature into the sketch result"
+                       change_visual_attributes="true"/>
+          </radio>
+          <radio id="false"
+                 title="Break connection with the original shape"
+                 tooltip="A sketch entity will be created without connection to the selected shape.">
+            <boolvalue id="make_fixed"
+                       label="Make projected curve fixed"
+                       default="true"
+                       tooltip="Assign the Fixed constraint to the result of projection"/>
+          </radio>
+        </radiobox>
         <validator id="PartSet_ProjectionSelection"/>
       </feature>
 
         </integervalue>
         <validator id="PartSet_MultyTranslationSelection" />
       </feature>
+
+      <!-- Offset curve -->
+      <feature id="SketchOffset"
+               title="Offset"
+               tooltip="Offset a curve to a distance"
+               icon="icons/Sketch/offset.png"
+               helpfile="offsetFeature.html">
+        <sketch_multi_selector id="segments"
+                               label="Edges"
+                               tooltip="Select edges to offset"
+                               shape_types="Edges"
+                               use_external="true"
+                               greed="true">
+          <validator id="SketchPlugin_CopyValidator" />
+        </sketch_multi_selector>
+        <doublevalue id="offset_value"
+                     label="Offset value"
+                     tooltip="Offset value"
+                     default="1" min="0.000001"
+                     use_reset="false">
+          <validator id="GeomValidators_Positive" parameters="1e-07"/>
+        </doublevalue>
+        <boolvalue id="reversed"
+                   label="Reversed"
+                   tooltip="Reverse the offset"
+                   default="false"
+                   obligatory="0"/>
+        <action id="add_wire"
+                label="Select wire"
+                tooltip="Add the list of segments composing a wire with the selected items through the coincidence by boundary points"/>
+      </feature>
     </group>
 
     <group id="Dimensional constraints">
index 5dba33dca3c35faa283c413dc7922875c8ee40d3..5df25991e0390edf175f69911df7034b74cc0499 100644 (file)
@@ -56,6 +56,7 @@ SET(SKETCHSOLVER_CONSTRAINT_HEADERS
     SketchSolver_ConstraintMultiRotation.h
     SketchSolver_ConstraintMultiTranslation.h
     SketchSolver_ConstraintMovement.h
+    SketchSolver_ConstraintOffset.h
 )
 
 SET(SKETCHSOLVER_SOURCES
@@ -81,6 +82,7 @@ SET(SKETCHSOLVER_CONSTRAINT_SOURCES
     SketchSolver_ConstraintMultiRotation.cpp
     SketchSolver_ConstraintMultiTranslation.cpp
     SketchSolver_ConstraintMovement.cpp
+    SketchSolver_ConstraintOffset.cpp
 )
 
 SET(SKETCHSOLVER_LIBRARIES
index f69cd48b54ebe71c270fb2df7885fe7582747948..db330f5dd3d5038d9b6c88e881ddc226be955606 100644 (file)
 
 #include <GeomAPI_BSpline2d.h>
 #include <GeomAPI_Pnt2d.h>
-#include <GeomAPI_XY.h>
 
 #include <cmath>
 
 namespace GCS
 {
 
-DeriVector2 BSplineImpl::Value(double u, double du, double* derivparam)
-{
-  if (!isCacheValid())
-    rebuildCache();
-
-  std::shared_ptr<GeomAPI_Pnt2d> aValue;
-  std::shared_ptr<GeomAPI_XY> aDeriv;
-  myCurve->D1(u, aValue, aDeriv);
-
-  // calculate the derivative on solver's parameter
-  std::shared_ptr<GeomAPI_Pnt2d> aValueDeriv(new GeomAPI_Pnt2d(0.0, 0.0));
-  bool hasParam = false;
-  std::list<GeomPnt2dPtr> aPolesDeriv;
-  for (GCS::VEC_P::iterator anIt = poles.begin(); anIt != poles.end(); ++anIt) {
-    double x = 0.0, y = 0.0;
-    if (anIt->x == derivparam) {
-      x = 1.0;
-      hasParam = true;
-    }
-    else if (anIt->y == derivparam) {
-      y = 1.0;
-      hasParam = true;
+  static void periodicNormalization(double& theParam, double thePeriodStart, double thePeriodEnd)
+  {
+    double aPeriod = thePeriodEnd - thePeriodStart;
+    if (aPeriod > tolerance) {
+      theParam = std::max(thePeriodStart,
+                          theParam + aPeriod * std::ceil((thePeriodStart - theParam) / aPeriod));
     }
-         aPolesDeriv.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(x, y)));
-  }
-  if (hasParam) {
-    // use non-periodic curve, because the most of B-spline coefficients are 0,
-    // thus, it is not necessary to keep original knots and multiplicities to get correct value
-    std::shared_ptr<GeomAPI_BSpline2d> aCurveDeriv(
-        new GeomAPI_BSpline2d(degree, aPolesDeriv, myCachedWeights));
-    aCurveDeriv->D0(u, aValueDeriv);
   }
 
-  return DeriVector2(aValue->x(), aValue->y(),
-                     aValueDeriv->x() + aDeriv->x() * du, aValueDeriv->y() + aDeriv->y() * du);
+
+DeriVector2 BSplineImpl::Value(double u, double du, double* derivparam)
+{
+  DeriVector2 value, deriv;
+  d1(u, derivparam, value, deriv);
+  return value.sum(GCS::DeriVector2(0., 0., deriv.x, deriv.y).mult(du));
 }
 
 DeriVector2 BSplineImpl::CalculateNormal(Point &p, double* derivparam)
 {
-  if (!isCacheValid())
-    rebuildCache();
-
   double u = 0.0;
-  if (!myCurve->parameter(GeomPnt2dPtr(new GeomAPI_Pnt2d(*p.x, *p.y)), 1e100, u))
+  if (!parameter(p, u))
     return DeriVector2();
 
-  std::shared_ptr<GeomAPI_Pnt2d> aValue;
-  std::shared_ptr<GeomAPI_XY> aDeriv;
-  myCurve->D1(u, aValue, aDeriv);
-
-  DeriVector2 norm(aDeriv->x(), aDeriv->y(), 0.0, 0.0);
-  return norm.rotate90ccw();
+  DeriVector2 value, deriv;
+  d1(u, derivparam, value, deriv);
+  return deriv.rotate90ccw();
 }
 
 BSplineImpl* BSplineImpl::Copy()
@@ -88,49 +61,120 @@ BSplineImpl* BSplineImpl::Copy()
   return new BSplineImpl(*this);
 }
 
+void BSplineImpl::d1(double theU,
+                     double* theDerivParam,
+                     GCS::DeriVector2& theValue,
+                     GCS::DeriVector2& theDerivative)
+{
+  int aSpan = spanIndex(theU);
+  std::vector<GCS::DeriVector2> aPoles;
+  std::vector<double> aWeights;
+  spanPolesAndWeights(aSpan, theDerivParam, aPoles, aWeights);
+  performDeBoor(theU, aSpan, aPoles, aWeights, theValue, theDerivative);
+}
 
-bool BSplineImpl::isCacheValid() const
+int BSplineImpl::spanIndex(double& u)
 {
-  // curve has to be initialized
-  bool isValid = myCurve.get() && !myCurve->isNull();
-
-  static const double THE_TOLERANCE = 1.e-7;
-  // compare poles
-  isValid = isValid && poles.size() == myCachedPoles.size();
-  std::list<GeomPnt2dPtr>::const_iterator aCachePIt = myCachedPoles.begin();
-  GCS::VEC_P::const_iterator aPolesIt = poles.begin();
-  for (; isValid && aPolesIt != poles.end(); ++aPolesIt, ++aCachePIt) {
-    isValid = isValid && fabs((*aCachePIt)->x() - *aPolesIt->x) < THE_TOLERANCE
-                      && fabs((*aCachePIt)->y() - *aPolesIt->y) < THE_TOLERANCE;
+  if (myFlatKnots.empty()) {
+    // fill flat knots indices
+    for (int i = 0; i < (int)mult.size(); ++i)
+      myFlatKnots.resize(myFlatKnots.size() + mult[i], *knots[i]);
+    if (periodic) {
+      // additional knots at the beginning and the end to complete periodity
+      int anExtraBegin = degree + 1 - mult.front();
+      int anExtraEnd = degree + 1 - mult.back();
+      double aPeriod = *knots.back() - *knots.front();
+      VEC_D aNewFlatKnots;
+      aNewFlatKnots.reserve(myFlatKnots.size() + (size_t)(anExtraBegin + anExtraEnd));
+      auto it = myFlatKnots.end() - mult.back() - anExtraBegin;
+      while (anExtraBegin > 0) {
+        aNewFlatKnots.push_back(*(it++) - aPeriod);
+        --anExtraBegin;
+      }
+      aNewFlatKnots.insert(aNewFlatKnots.end(), myFlatKnots.begin(), myFlatKnots.end());
+      it = myFlatKnots.begin() + mult.front();
+      while (anExtraEnd > 0) {
+        aNewFlatKnots.push_back(*(it++) + aPeriod);
+        --anExtraEnd;
+      }
+      myFlatKnots = aNewFlatKnots;
+    }
   }
 
-  // compare weights
-  isValid = isValid && weights.size() == myCachedWeights.size();
-  std::list<double>::const_iterator aCacheWIt = myCachedWeights.begin();
-  GCS::VEC_pD::const_iterator aWeightsIt = weights.begin();
-  for (; isValid && aWeightsIt != weights.end(); ++aWeightsIt, ++aCacheWIt)
-    isValid = isValid && fabs(*aCacheWIt - **aWeightsIt) < THE_TOLERANCE;
+  if (periodic)
+    periodicNormalization(u, *knots.front(), *knots.back());
+
+  int anIndex = 0;
+  for (int i = 1; i < (int)knots.size() - 1; ++i) {
+    if (u <= *knots[i])
+      break;
+    anIndex += mult[i];
+  }
+  return anIndex;
+}
 
-  return isValid;
+void BSplineImpl::spanPolesAndWeights(int theSpanIndex,
+                                      double* theDerivParam,
+                                      std::vector<GCS::DeriVector2>& thePoles,
+                                      std::vector<double>& theWeights) const
+{
+  thePoles.reserve(degree + 1);
+  theWeights.reserve(degree + 1);
+  for (int i = theSpanIndex; i <= theSpanIndex + degree; ++i) {
+    // optimization: weighted pole
+    int idx = i % (int)poles.size();
+    thePoles.push_back(GCS::DeriVector2(poles[idx], theDerivParam).mult(*weights[idx]));
+    theWeights.push_back(*weights[idx]);
+  }
+}
+
+void BSplineImpl::performDeBoor(double theU,
+                                int theSpanIndex,
+                                std::vector<GCS::DeriVector2>& thePoles,
+                                std::vector<double>& theWeights,
+                                GCS::DeriVector2& theValue,
+                                GCS::DeriVector2& theDerivative) const
+{
+  std::vector<GCS::DeriVector2> aPDeriv(thePoles.size(), DeriVector2());
+  std::vector<double> aWDeriv(theWeights.size(), 0.0);
+  for (int i = 0; i < degree; ++i) {
+    for (int j = degree; j > i; --j) {
+      double denom = (myFlatKnots[theSpanIndex + j + degree - i] -
+                      myFlatKnots[theSpanIndex + j]);
+      double a = (theU - myFlatKnots[theSpanIndex + j]) / denom;
+      aPDeriv[j] = aPDeriv[j].linCombi(a, aPDeriv[j - 1], 1.0 - a).sum(
+                   thePoles[j].subtr(thePoles[j - 1]).mult(1.0 / denom));
+      aWDeriv[j] = aWDeriv[j] * a + aWDeriv[j - 1] * (1.0 - a)
+                 + (theWeights[j] - theWeights[j - 1]) / denom;
+      thePoles[j] = thePoles[j].linCombi(a, thePoles[j - 1], 1.0 - a);
+      theWeights[j] = theWeights[j] * a + theWeights[j - 1] * (1.0 - a);
+    }
+  }
+  double w = 1 / theWeights[degree];
+  theValue = thePoles[degree].mult(w);
+  theDerivative = aPDeriv[degree].subtr(theValue.mult(aWDeriv[degree])).mult(w);
 }
 
-void BSplineImpl::rebuildCache()
+bool BSplineImpl::parameter(const Point& thePoint, double& theParam) const
 {
-  myCachedPoles.clear();
-  myCachedWeights.clear();
-  myCachedKnots.clear();
-  myCachedMultiplicities.clear();
-
-  for (GCS::VEC_P::iterator anIt = poles.begin(); anIt != poles.end(); ++anIt)
-    myCachedPoles.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(*anIt->x, *anIt->y)));
-  for (GCS::VEC_pD::iterator anIt = weights.begin(); anIt != weights.end(); ++anIt)
-    myCachedWeights.push_back(**anIt);
-  for (GCS::VEC_pD::iterator anIt = knots.begin(); anIt != knots.end(); ++anIt)
-    myCachedKnots.push_back(**anIt);
-  myCachedMultiplicities.assign(mult.begin(), mult.end());
-
-  myCurve.reset(new GeomAPI_BSpline2d(degree, myCachedPoles, myCachedWeights,
-                                      myCachedKnots, myCachedMultiplicities, periodic));
+  std::list<GeomPnt2dPtr> aPoles;
+  std::list<double> aWeights;
+  std::list<double> aKnots;
+  std::list<int> aMults;
+
+  for (GCS::VEC_P::const_iterator anIt = poles.begin(); anIt != poles.end(); ++anIt)
+    aPoles.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(*anIt->x, *anIt->y)));
+  for (GCS::VEC_pD::const_iterator anIt = weights.begin(); anIt != weights.end(); ++anIt)
+    aWeights.push_back(**anIt);
+  for (GCS::VEC_pD::const_iterator anIt = knots.begin(); anIt != knots.end(); ++anIt)
+    aKnots.push_back(**anIt);
+  aMults.assign(mult.begin(), mult.end());
+
+  std::shared_ptr<GeomAPI_BSpline2d> aCurve(
+      new GeomAPI_BSpline2d(degree, aPoles, aWeights, aKnots, aMults, periodic));
+
+  GeomPnt2dPtr aPoint(new GeomAPI_Pnt2d(*thePoint.x, *thePoint.y));
+  return aCurve->parameter(aPoint, 1e100, theParam);
 }
 
 } // namespace GCS
index c83e1a9ddde3eacbfb8f664161ffcc2439e44020..3b734538fa53fd76854c147b8a0153c4809fb68a 100644 (file)
 
 #include <PlaneGCSSolver_Defs.h>
 
-#include <list>
-#include <memory>
-
-class GeomAPI_BSpline2d;
-class GeomAPI_Pnt2d;
-
 namespace GCS {
   /// \brife SHAPER's implementation of B-spline curves in PlaneGCS solver
   class BSplineImpl : public BSpline
@@ -39,17 +33,32 @@ namespace GCS {
     virtual BSplineImpl* Copy();
 
   private:
-    /// Verify the cached curve satisfies to the parameters
-    bool isCacheValid() const;
-    /// Poles or weights are changed, cache curve has to be rebuilt
-    void rebuildCache();
+    /// Return the index of start knot for the given parameter.
+    /// Parameter is updated accordingly, if the B-spline curve is periodic
+    /// and the parameter is out of period.
+    int spanIndex(double& u);
+
+    /// Collect the list of poles and their weights affecting the given span
+    void spanPolesAndWeights(int theSpanIndex,
+                             double* theDerivParam,
+                             std::vector<GCS::DeriVector2>& thePoles,
+                             std::vector<double>& theWeights) const;
+
+    /// Execute De Boor algorithm to calculate B-spline curve's value
+    void performDeBoor(double theU, int theSpanIndex,
+                       std::vector<GCS::DeriVector2>& thePoles, std::vector<double>& theWeights,
+                       GCS::DeriVector2& theValue, GCS::DeriVector2& theDerivative) const;
+
+    /// Calculate the value and the first derivative for the given parameter on B-spline
+    void d1(double theU, double* theDerivParam,
+            GCS::DeriVector2& theValue, GCS::DeriVector2& theDerivative);
+
+    /// Find the parameter on B-spline corresponding to the given point
+    /// \return \c false if it is unable to calculate the parameter
+    bool parameter(const Point& thePoint, double& theParam) const;
 
   private:
-    std::shared_ptr<GeomAPI_BSpline2d> myCurve; /// cached B-spline curve
-    std::list<std::shared_ptr<GeomAPI_Pnt2d> > myCachedPoles; /// cached B-spline poles
-    std::list<double> myCachedWeights; /// cached B-spline weights
-    std::list<double> myCachedKnots; /// cached B-spline knots
-    std::list<int> myCachedMultiplicities; /// cached B-spline multiplicities
+    VEC_D myFlatKnots; /// indices of knots duplicated by multiplicity
   };
 }
 
index 0ecc2291a99583d22f379100b437ca4c4aee24bc..6d6d5beab268a1f975d37c34519d02d9dd4cb0b3 100644 (file)
@@ -114,7 +114,7 @@ static bool hasReference(std::shared_ptr<SketchPlugin_Feature> theFeature,
   for (std::set<AttributePtr>::const_iterator aRefIt = aRefs.begin();
        aRefIt != aRefs.end(); ++aRefIt) {
      FeaturePtr anOwner = ModelAPI_Feature::feature((*aRefIt)->owner());
-     if (anOwner && anOwner->getKind() == theFeatureKind)
+     if (anOwner && !anOwner->isMacro() && anOwner->getKind() == theFeatureKind)
        return true;
   }
   return false;
index cbcb03bc87a72e84dca9a147f02dd66c5cb5e0d4..fb7185c46e2659626e262cbd219a3fa2f3282039 100644 (file)
@@ -40,6 +40,7 @@
 #include <SketchSolver_ConstraintTangent.h>
 #include <SketchSolver_ConstraintMultiRotation.h>
 #include <SketchSolver_ConstraintMultiTranslation.h>
+#include <SketchSolver_ConstraintOffset.h>
 
 #include <SketchPlugin_Arc.h>
 #include <SketchPlugin_BSpline.h>
@@ -63,6 +64,7 @@
 #include <SketchPlugin_Line.h>
 #include <SketchPlugin_MultiRotation.h>
 #include <SketchPlugin_MultiTranslation.h>
+#include <SketchPlugin_Offset.h>
 #include <SketchPlugin_Point.h>
 
 #include <GeomAPI_BSpline2d.h>
@@ -186,6 +188,8 @@ SolverConstraintPtr PlaneGCSSolver_Tools::createConstraint(ConstraintPtr theCons
     return SolverConstraintPtr(new SketchSolver_ConstraintAngle(theConstraint));
   } else if (theConstraint->getKind() == SketchPlugin_ConstraintPerpendicular::ID()) {
     return SolverConstraintPtr(new SketchSolver_ConstraintPerpendicular(theConstraint));
+  } else if (theConstraint->getKind() == SketchPlugin_Offset::ID()) {
+    return SolverConstraintPtr(new SketchSolver_ConstraintOffset(theConstraint));
   }
   // All other types of constraints
   return SolverConstraintPtr(new SketchSolver_Constraint(theConstraint));
diff --git a/src/SketchSolver/SketchSolver_ConstraintOffset.cpp b/src/SketchSolver/SketchSolver_ConstraintOffset.cpp
new file mode 100644 (file)
index 0000000..dbe4197
--- /dev/null
@@ -0,0 +1,40 @@
+// 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 <SketchSolver_ConstraintOffset.h>
+
+
+void SketchSolver_ConstraintOffset::getAttributes(
+    EntityWrapperPtr&,
+    std::vector<EntityWrapperPtr>&)
+{
+}
+
+void SketchSolver_ConstraintOffset::process()
+{
+  cleanErrorMsg();
+}
+
+
+void SketchSolver_ConstraintOffset::update()
+{
+  cleanErrorMsg();
+  remove();
+  process();
+}
diff --git a/src/SketchSolver/SketchSolver_ConstraintOffset.h b/src/SketchSolver/SketchSolver_ConstraintOffset.h
new file mode 100644 (file)
index 0000000..45d760b
--- /dev/null
@@ -0,0 +1,48 @@
+// 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 SketchSolver_ConstraintOffset_H_
+#define SketchSolver_ConstraintOffset_H_
+
+#include <SketchSolver_Constraint.h>
+
+/** \class   SketchSolver_ConstraintOffset
+ *  \ingroup Plugins
+ *  \brief   Convert offset to the solver's data model
+ */
+class SketchSolver_ConstraintOffset : public SketchSolver_Constraint
+{
+public:
+  /// Constructor based on SketchPlugin constraint
+  SketchSolver_ConstraintOffset(ConstraintPtr theConstraint) :
+      SketchSolver_Constraint(theConstraint)
+  {}
+
+  /// \brief Update constraint
+  virtual void update();
+
+protected:
+  /// \brief Converts SketchPlugin constraint to a list of SolveSpace constraints
+  virtual void process();
+
+  /// \brief Generate list of entities of mirror constraint
+  virtual void getAttributes(EntityWrapperPtr&, std::vector<EntityWrapperPtr>&);
+};
+
+#endif
index a8d3dc4f56eada6de75cdd34df48f68835a34e94..8a123c733f78bdc00e018658afe14e8d04e993f1 100644 (file)
@@ -44,6 +44,7 @@ SET(PROJECT_HEADERS
     SketcherPrs_Mirror.h
     SketcherPrs_Transformation.h
     SketcherPrs_Angle.h
+       SketcherPrs_Offset.h
 )
 
 SET(PROJECT_SOURCES
@@ -67,6 +68,7 @@ SET(PROJECT_SOURCES
     SketcherPrs_Mirror.cpp
     SketcherPrs_Transformation.cpp
     SketcherPrs_Angle.cpp
+       SketcherPrs_Offset.cpp
 )
 
 SET(PROJECT_LIBRARIES
@@ -102,6 +104,7 @@ SET(PROJECT_PICTURES
     icons/mirror.png
     icons/rotate.png
     icons/translate.png
+    icons/offset.png
 )
 
 ADD_DEFINITIONS(-DSKETCHERPRS_EXPORTS ${OpenCASCADE_DEFINITIONS} -D_CRT_SECURE_NO_WARNINGS)
index 1c4187aaf73f966410419ee40ba577b937fb0eb4..59d73c0f808f80f0f4890aa49665b7429e922f5e 100644 (file)
@@ -33,6 +33,7 @@
 #include "SketcherPrs_Mirror.h"
 #include "SketcherPrs_Transformation.h"
 #include "SketcherPrs_Angle.h"
+#include "SketcherPrs_Offset.h"
 
 // Macros for constraint presentation definition
 #define CONSTRAINT_PRS_IMPL(NAME, CLASS) \
@@ -66,6 +67,7 @@ CONSTRAINT_PRS_IMPL(coincidentConstraint, SketcherPrs_Coincident);
 CONSTRAINT_PRS_IMPL(lengthDimensionConstraint, SketcherPrs_LengthDimension);
 CONSTRAINT_PRS_IMPL(angleConstraint, SketcherPrs_Angle);
 CONSTRAINT_PRS_IMPL(radiusConstraint, SketcherPrs_Radius);
+CONSTRAINT_PRS_IMPL(offsetObject, SketcherPrs_Offset);
 
 // Non-standard constraints definition
 AISObjectPtr SketcherPrs_Factory::horisontalConstraint(ModelAPI_Feature* theConstraint,
index 9caf0a1a5af91a57940c58cb3ebd3136cb769726..286ce0158c251a517f3e5d179da00480cb7ec4fe 100644 (file)
@@ -135,6 +135,12 @@ public:
   /// \param thePlane the current sketch plane
   /// \param thePrevious the previous presentation
   GET_CONSTRAINT_PRS(radiusConstraint)
+
+  /// Creates radius dimension presentation
+  /// \param theConstraint the constraint
+  /// \param thePlane the current sketch plane
+  /// \param thePrevious the previous presentation
+  GET_CONSTRAINT_PRS(offsetObject)
 };
 
 #endif
diff --git a/src/SketcherPrs/SketcherPrs_Offset.cpp b/src/SketcherPrs/SketcherPrs_Offset.cpp
new file mode 100644 (file)
index 0000000..a9d3ffb
--- /dev/null
@@ -0,0 +1,118 @@
+// Copyright (C) 2014-2019  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 "SketcherPrs_Offset.h"
+#include "SketcherPrs_Tools.h"
+#include "SketcherPrs_PositionMgr.h"
+
+#include <SketchPlugin_Offset.h>
+
+#include <ModelAPI_AttributeRefList.h>
+#include <ModelAPI_AttributeDouble.h>
+
+#include <Graphic3d_AspectLine3d.hxx>
+#include <Prs3d_Root.hxx>
+
+
+IMPLEMENT_STANDARD_RTTIEXT(SketcherPrs_Offset, SketcherPrs_SymbolPrs);
+
+static Handle(Image_AlienPixMap) MyPixMap;
+
+SketcherPrs_Offset::SketcherPrs_Offset(ModelAPI_Feature* theConstraint,
+  SketchPlugin_Sketch* theSketcher)
+  : SketcherPrs_SymbolPrs(theConstraint, theSketcher)
+{
+}
+
+bool SketcherPrs_Offset::IsReadyToDisplay(ModelAPI_Feature* theConstraint,
+  const std::shared_ptr<GeomAPI_Ax3>&/* thePlane*/)
+{
+  bool aReadyToDisplay = false;
+
+  AttributeDoublePtr aValueAttr = theConstraint->real(SketchPlugin_Offset::VALUE_ID());
+  if (aValueAttr->isInitialized()) {
+    AttributeRefListPtr aSelectedEdges = theConstraint->reflist(SketchPlugin_Offset::ENTITY_A());
+    aReadyToDisplay = (aSelectedEdges->list().size() > 0);
+    if (aReadyToDisplay) {
+      AttributeRefListPtr aOffcetEdges = theConstraint->reflist(SketchPlugin_Offset::ENTITY_B());
+      aReadyToDisplay = (aOffcetEdges->list().size() > 0);
+    }
+  }
+  return aReadyToDisplay;
+}
+
+bool SketcherPrs_Offset::updateIfReadyToDisplay(double theStep, bool withColor) const
+{
+  if (!IsReadyToDisplay(myConstraint, plane()))
+    return false;
+  if (!plane())
+    return false;
+
+  // Set points of the presentation
+  SketcherPrs_PositionMgr* aMgr = SketcherPrs_PositionMgr::get();
+
+  AttributeRefListPtr aSelectedEdges = myConstraint->reflist(SketchPlugin_Offset::ENTITY_A());
+  int aNb = aSelectedEdges->size();
+
+  AttributeRefListPtr aOffcetEdges = myConstraint->reflist(SketchPlugin_Offset::ENTITY_B());
+  int aOffNb = aOffcetEdges->size();
+
+  myPntArray = new Graphic3d_ArrayOfPoints(aNb + aOffNb, withColor);
+  int i;
+  ObjectPtr aObj;
+  gp_Pnt aP1;
+  // get position for each source object
+  for (i = 0; i < aNb; i++) {
+    aObj = aSelectedEdges->object(i);
+    if (SketcherPrs_Tools::getShape(aObj).get() == NULL)
+      continue;
+    aP1 = aMgr->getPosition(aObj, this, theStep);
+    myPntArray->SetVertice(i + 1, aP1);
+  }
+  for (i = aNb; i < aNb + aOffNb; i++) {
+    aObj = aOffcetEdges->object(i - aNb);
+    if (SketcherPrs_Tools::getShape(aObj).get() == NULL)
+      continue;
+    aP1 = aMgr->getPosition(aObj, this, theStep);
+    myPntArray->SetVertice(i + 1, aP1);
+  }
+  return true;
+}
+
+void SketcherPrs_Offset::drawLines(const Handle(Prs3d_Presentation)& thePrs,
+  Quantity_Color theColor) const
+{
+  AttributeRefListPtr aSelectedEdges = myConstraint->reflist(SketchPlugin_Offset::ENTITY_A());
+  if (aSelectedEdges.get() == NULL)
+    return;
+  AttributeRefListPtr aOffcetEdges = myConstraint->reflist(SketchPlugin_Offset::ENTITY_B());
+  if (aOffcetEdges.get() == NULL)
+    return;
+
+  if (aSelectedEdges->size() == 0)
+    return;
+
+  if (aOffcetEdges->size() == 0)
+    return;
+
+  // Draw source objects
+  drawListOfShapes(aSelectedEdges, thePrs, theColor);
+  // Draw offcet objects
+  drawListOfShapes(aOffcetEdges, thePrs, theColor);
+}
diff --git a/src/SketcherPrs/SketcherPrs_Offset.h b/src/SketcherPrs/SketcherPrs_Offset.h
new file mode 100644 (file)
index 0000000..04e7d76
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (C) 2014-2019  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 SketcherPrs_Offset_H
+#define SketcherPrs_Offset_H
+
+#include "SketcherPrs_SymbolPrs.h"
+
+
+DEFINE_STANDARD_HANDLE(SketcherPrs_Offset, SketcherPrs_SymbolPrs)
+
+/**
+* \ingroup GUI
+* A redefinition of standard AIS Interactive Object in order to provide
+* presentation of Equal constraint
+*/
+class SketcherPrs_Offset : public SketcherPrs_SymbolPrs
+{
+public:
+  /// Constructor
+  /// \param theConstraint a constraint feature
+  /// \param theSketcher a sketcher object
+  Standard_EXPORT SketcherPrs_Offset(ModelAPI_Feature* theConstraint,
+    SketchPlugin_Sketch* theSketcher);
+  DEFINE_STANDARD_RTTIEXT(SketcherPrs_Offset, SketcherPrs_SymbolPrs)
+
+    /// Returns true if the constraint feature arguments are correcly
+    /// filled to build AIS presentation
+    /// \param theConstraint a constraint feature
+    /// \param thePlane a coordinate plane of current sketch
+    /// \return boolean result value
+    static bool IsReadyToDisplay(ModelAPI_Feature* theConstraint,
+      const std::shared_ptr<GeomAPI_Ax3>& thePlane);
+protected:
+
+  virtual const char* iconName() const { return "offset.png"; }
+
+  virtual void drawLines(const Handle(Prs3d_Presentation)& thePrs, Quantity_Color theColor) const;
+
+  /// Update myPntArray according to presentation positions
+  /// \return true in case of success
+  virtual bool updateIfReadyToDisplay(double theStep, bool withColor) const;
+};
+
+#endif
\ No newline at end of file
index d535a6d7f362c3d27f2d5db3d7a40a5d8996f273..cdf0a2391fe6df19f42dac01a7a32f4da9681f2d 100644 (file)
@@ -95,6 +95,11 @@ ObjectPtr getResult(ModelAPI_Feature* theFeature, const std::string& theAttrName
 std::shared_ptr<GeomAPI_Shape> getShape(ObjectPtr theObject)
 {
   ResultConstructionPtr aRes = std::dynamic_pointer_cast<ModelAPI_ResultConstruction>(theObject);
+  if (!aRes.get()) {
+    FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theObject);
+    if (aFeature.get())
+      aRes = std::dynamic_pointer_cast<ModelAPI_ResultConstruction>(aFeature->lastResult());
+  }
   if (aRes.get() != NULL && aRes->data()->isValid()) {
     /// essential check as it is called in openGl thread
     return aRes->shape();
diff --git a/src/SketcherPrs/icons/offset.png b/src/SketcherPrs/icons/offset.png
new file mode 100644 (file)
index 0000000..0e0f737
Binary files /dev/null and b/src/SketcherPrs/icons/offset.png differ
index 7145c235fe18a50aac28446129ec023d490ce8c0..d5b9bf15a782ee514f2f9a709c650e6fd01d5e3b 100644 (file)
@@ -94,6 +94,7 @@ void XGUI_ContextMenuMgr::createActions()
 
   aAction = ModuleBase_Tools::createAction(QIcon(":pictures/rename_edit.png"), tr("Rename"),
                                            aDesktop, this, SLOT(onRename()));
+  aAction->setShortcut(Qt::Key_F2);
   addAction("RENAME_CMD", aAction);
 
   aAction = ModuleBase_Tools::createAction(QIcon(":pictures/move_to_end.png"),
index 3bca93a90f19e6fc9bd569c22014a91fecc42054..927d772cb5f5f7dde3da9e5dd31bbbcacd99f08a 100644 (file)
@@ -65,8 +65,8 @@ public:
   /// Constructor
   /// \param theParent the parent to be deleted when the parent is deleted
   /// \param theOperationMgr the class to perform deletion
-  XGUI_ShortCutListener(QObject* theParent, XGUI_OperationMgr* theOperationMgr)
-    : QObject(theParent), myOperationMgr(theOperationMgr), myIsActive(false)
+  XGUI_ShortCutListener(XGUI_OperationMgr* theOperationMgr)
+    : QObject(theOperationMgr), myOperationMgr(theOperationMgr), myIsActive(false)
   {
     qApp->installEventFilter(this);
   }
@@ -76,61 +76,69 @@ public:
   void setActive(const bool theIsActive) { myIsActive = theIsActive; }
 
   /// Redefinition of virtual function to process Delete key release
-  virtual bool eventFilter(QObject *theObject, QEvent *theEvent)
-  {
-    bool isAccepted = false;
-
-    if (myIsActive) {
-      // Do not process keys for modal dialogues: all keys has to be processed within the dialog
-      // There is only one exception: ModuleBase_EditorDialog
-      QWindow* aWnd = qApp->modalWindow();
-      QString aName = "NoModal";
-      if (aWnd) {
-        if (!aWnd->objectName().startsWith("ModuleBase_EditorDialog"))
-          aName = aWnd->objectName();
-      }
-      if (aName == "NoModal") {
-        if (theEvent->type() == QEvent::KeyRelease) {
-          QKeyEvent* aKeyEvent = dynamic_cast<QKeyEvent*>(theEvent);
-          if (aKeyEvent) {
-            myOperationMgr->setSHIFTPressed(aKeyEvent->modifiers() & Qt::ShiftModifier);
-            switch (aKeyEvent->key()) {
-            case Qt::Key_Delete:
-              isAccepted = myOperationMgr->onProcessDelete(theObject);
-              break;
-            default:
-              isAccepted = myOperationMgr->onKeyReleased(theObject, aKeyEvent);
-              break;
-            }
+  virtual bool eventFilter(QObject *theObject, QEvent *theEvent);
+
+private:
+  XGUI_OperationMgr* myOperationMgr; /// processor for key event
+  bool myIsActive; /// boolean state whether the event filter perform own signal processing
+};
+
+bool XGUI_ShortCutListener::eventFilter(QObject *theObject, QEvent *theEvent)
+{
+  bool isAccepted = false;
+
+  if (myIsActive) {
+    // Do not process keys for modal dialogues: all keys has to be processed within the dialog
+    // There is only one exception: ModuleBase_EditorDialog
+    QWindow* aWnd = qApp->modalWindow();
+    QString aName = "NoModal";
+    if (aWnd) {
+      if (!aWnd->objectName().startsWith("ModuleBase_EditorDialog"))
+        aName = aWnd->objectName();
+    }
+    if (aName == "NoModal") {
+      if (theEvent->type() == QEvent::KeyRelease) {
+        QKeyEvent* aKeyEvent = dynamic_cast<QKeyEvent*>(theEvent);
+        if (aKeyEvent) {
+          myOperationMgr->setSHIFTPressed(aKeyEvent->modifiers() & Qt::ShiftModifier);
+          switch (aKeyEvent->key()) {
+          case Qt::Key_Delete:
+            isAccepted = myOperationMgr->onProcessDelete(theObject);
+            break;
+          case Qt::Key_F2:
+            myOperationMgr->xworkshop()->objectBrowser()->onEditItem();
+            isAccepted = true;
+            break;
+          default:
+            isAccepted = myOperationMgr->onKeyReleased(theObject, aKeyEvent);
+            break;
           }
         }
-        else if (theEvent->type() == QEvent::KeyPress) {
-          if (myOperationMgr->hasOperation()) {
-            QKeyEvent* aKeyEvent = dynamic_cast<QKeyEvent*>(theEvent);
-            myOperationMgr->setSHIFTPressed(aKeyEvent->modifiers() & Qt::ShiftModifier);
-            isAccepted = myOperationMgr->onKeyPressed(theObject, aKeyEvent);
-          }
+      }
+      else if (theEvent->type() == QEvent::KeyPress) {
+        if (myOperationMgr->hasOperation()) {
+          QKeyEvent* aKeyEvent = dynamic_cast<QKeyEvent*>(theEvent);
+          myOperationMgr->setSHIFTPressed(aKeyEvent->modifiers() & Qt::ShiftModifier);
+          isAccepted = myOperationMgr->onKeyPressed(theObject, aKeyEvent);
         }
       }
     }
-    if (!isAccepted)
-      isAccepted = QObject::eventFilter(theObject, theEvent);
-    return isAccepted;
   }
+  if (!isAccepted)
+    isAccepted = QObject::eventFilter(theObject, theEvent);
+  return isAccepted;
+}
+
 
-private:
-  XGUI_OperationMgr* myOperationMgr; /// processor for key event
-  bool myIsActive; /// boolean state whether the event filter perform own signal processing
-};
 
 XGUI_OperationMgr::XGUI_OperationMgr(QObject* theParent,
-                                     ModuleBase_IWorkshop* theWorkshop)
+  ModuleBase_IWorkshop* theWorkshop)
 : QObject(theParent), myWorkshop(theWorkshop), myActiveMessageBox(0), mySHIFTPressed(false)
 {
   /// we need to install filter to the application in order to react to 'Delete' key button
   /// this key can not be a short cut for a corresponded action because we need to set
   /// the actions priority
-  myShortCutListener = new XGUI_ShortCutListener(theParent, this);
+  myShortCutListener = new XGUI_ShortCutListener(this);
 }
 
 XGUI_OperationMgr::~XGUI_OperationMgr()
@@ -930,3 +938,9 @@ QMessageBox* XGUI_OperationMgr::createInformationBox(const QString& theMessage)
 
   return aMessageBox;
 }
+
+XGUI_Workshop* XGUI_OperationMgr::xworkshop() const
+{
+  XGUI_ModuleConnector* aConnector = (XGUI_ModuleConnector*) myWorkshop;
+  return aConnector->workshop();
+}
index 7e03f56ef2562f29bc9992102be3deb8a1051536..15f89922d3c3855f85268e41cf8318ee7ac70351 100644 (file)
@@ -78,6 +78,9 @@ public:
   /// Current workshop
   ModuleBase_IWorkshop* workshop() const { return myWorkshop; }
 
+  /// Current workshop
+  XGUI_Workshop* xworkshop() const;
+
   /// Returns the current operation or NULL
   /// \return the current operation
   ModuleBase_Operation* currentOperation() const;
index 274964c1dc7181d3fe9cd2da257700d1aeb73518..19d750c8614fcc0fe78560323a0fc0d37829e4d9 100644 (file)
@@ -210,7 +210,7 @@ XGUI_Workshop::XGUI_Workshop(XGUI_SalomeConnector* theConnector)
 {
   mySelector = new XGUI_SelectionMgr(this);
   myModuleConnector = new XGUI_ModuleConnector(this);
-  myOperationMgr = new XGUI_OperationMgr(this, 0);
+  myOperationMgr = new XGUI_OperationMgr(this, myModuleConnector);
   ModuleBase_IWorkshop* aWorkshop = moduleConnector();
   // Has to be defined first in order to get errors and messages from other components
   myEventsListener = new XGUI_WorkshopListener(this);
@@ -273,8 +273,6 @@ XGUI_Workshop::XGUI_Workshop(XGUI_SalomeConnector* theConnector)
   //connect(myViewerProxy, SIGNAL(selectionChanged()),
   //        myActionsMgr,  SLOT(updateOnViewSelection()));
 
-  myOperationMgr->setWorkshop(aWorkshop);
-
   myErrorMgr = new XGUI_ErrorMgr(this, aWorkshop);
 
   connect(myOperationMgr, SIGNAL(operationResumed(ModuleBase_Operation*)),