]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
Task #3231: Sketcher Offset of a curve
authorArtem Zhidkov <Artem.Zhidkov@opencascade.com>
Mon, 25 May 2020 12:05:31 +0000 (15:05 +0300)
committerArtem Zhidkov <Artem.Zhidkov@opencascade.com>
Mon, 6 Jul 2020 08:28:06 +0000 (11:28 +0300)
* Implement the feature for the offset operation.
* Improve searching of coincidences in context of B-spline curve
* Decline offset if duplicated entities are selected.

45 files changed:
src/GeomAPI/GeomAPI_BSpline.cpp
src/GeomAPI/GeomAPI_BSpline.h
src/GeomAPI/GeomAPI_Edge.cpp
src/GeomAlgoAPI/GeomAlgoAPI.i
src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp
src/GeomAlgoAPI/GeomAlgoAPI_Offset.h
src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.cpp
src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.h
src/Model/Model_AttributeIntArray.cpp
src/SketchAPI/CMakeLists.txt
src/SketchAPI/SketchAPI.i
src/SketchAPI/SketchAPI_Offset.cpp [new file with mode: 0644]
src/SketchAPI/SketchAPI_Offset.h [new file with mode: 0644]
src/SketchAPI/SketchAPI_Sketch.cpp
src/SketchAPI/SketchAPI_Sketch.h
src/SketchAPI/SketchAPI_swig.h
src/SketchPlugin/CMakeLists.txt
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_Tools.cpp
src/SketchPlugin/SketchPlugin_Tools.h
src/SketchPlugin/SketchPlugin_Validators.cpp
src/SketchPlugin/Test/TestOffset1.py [new file with mode: 0644]
src/SketchPlugin/Test/TestOffset2.py [new file with mode: 0644]
src/SketchPlugin/doc/SketchPlugin.rst
src/SketchPlugin/doc/TUI_offset.rst [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/offset.png [new file with mode: 0644]
src/SketchPlugin/doc/offsetFeature.rst [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_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]

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 41cfa84a8b88710c496bf8656834ba84b93bf865..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();
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 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 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 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 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 d46066e6c03475fd08fb48e3a7fd46a93d5813d2..98bc2c6ab68649ccffe298263cc51dfaa39638f0 100644 (file)
@@ -71,6 +71,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"
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 a4a9bec2513c48ca0fff5531cbd46e7dd10f1288..2f6ffcdfab9befdf7692b55cb511d53a661b5fd5 100644 (file)
@@ -43,6 +43,7 @@
 #include <SketchPlugin_ConstraintVertical.h>
 #include <SketchPlugin_MacroBSpline.h>
 #include <SketchPlugin_SketchCopy.h>
+#include <SketchPlugin_Offset.h>
 #include <SketcherPrs_Tools.h>
 //--------------------------------------------------------------------------------------
 #include <ModelAPI_Events.h>
@@ -67,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"
@@ -889,6 +891,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 57d424760f8e787f6b0111cff83246bb11bf90bd..3911015dea0dff208139a6968572a1718082e881 100644 (file)
@@ -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;
@@ -370,6 +371,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 8c33ed0edf765c5ab1c78db15fb894d96213a9cc..aa51871dcc30652436fd14f040a746f4a7c3ee92 100644 (file)
@@ -64,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
@@ -118,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
@@ -324,6 +326,8 @@ ADD_UNIT_TESTS(
   TestMultiRotation05.py
   TestMultiRotationWithParameter.py
   TestMultiTranslation.py
+  TestOffset1.py
+  TestOffset2.py
   TestPresentation.py
   TestProjection.py
   TestProjectionBSpline.py
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 a2f0de04a067c8a5924f2b6655e57275ce9d236a..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>
@@ -284,6 +285,8 @@ FeaturePtr SketchPlugin_Plugin::createFeature(std::string theFeatureID)
     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();
@@ -362,7 +365,7 @@ std::shared_ptr<ModelAPI_FeatureStateMessage> SketchPlugin_Plugin
       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 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 4a6cc4b256d0ea557a979cac748e9c108e6eb94d..feaec5c663f2c320d12352567c584a2c7ece649e 100644 (file)
@@ -36,6 +36,7 @@
 #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"
@@ -513,6 +514,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()));
@@ -524,6 +526,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)
@@ -533,20 +541,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;
 }
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 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_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/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/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/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 76800d383ef97918b3e25e23c551f74191602523..ef62acfca3bb04f0e0c12e64477916fa5ed6fe81 100644 (file)
@@ -18,6 +18,7 @@
                 SketchConstraintCoincidence SketchConstraintCoincidenceInternal
                 SketchConstraintMirror SketchConstraintAngle
                 SketchMultiRotation SketchMultiTranslation
+                SketchOffset
                 SketchConstraintCollinear SketchConstraintMiddle"
         when_nested="accept abort"
         title="Sketch"
         </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 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