]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
bos #29476 Offset in sketch
authorjfa <jfa@opencascade.com>
Wed, 11 May 2022 10:57:50 +0000 (13:57 +0300)
committervsr <vsr@opencascade.com>
Thu, 2 Jun 2022 09:42:47 +0000 (12:42 +0300)
20 files changed:
src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp
src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h
src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp
src/GeomAlgoAPI/GeomAlgoAPI_Offset.h
src/Model/Model_AttributeIntArray.cpp
src/SketchAPI/SketchAPI_Offset.cpp
src/SketchAPI/SketchAPI_Offset.h
src/SketchAPI/SketchAPI_Sketch.cpp
src/SketchAPI/SketchAPI_Sketch.h
src/SketchPlugin/SketchPlugin_Offset.cpp
src/SketchPlugin/SketchPlugin_Offset.h
src/SketchPlugin/Test/TestOffset3.py [new file with mode: 0644]
src/SketchPlugin/doc/examples/offset.py
src/SketchPlugin/doc/images/Offset_panel.png
src/SketchPlugin/doc/offsetFeature.rst
src/SketchPlugin/icons/offset_arcs_32x32.png [new file with mode: 0644]
src/SketchPlugin/icons/offset_keep_distance_32x32.png [new file with mode: 0644]
src/SketchPlugin/icons/offset_lines_32x32.png [new file with mode: 0644]
src/SketchPlugin/plugin-Sketch.xml
src/SketchPlugin/tests.set

index 34673a7e9370764e1261e6c2267b51e1600286c6..6c6c9ab955b2afef61432e045861001229d72774 100644 (file)
 #include <GeomAlgoAPI_Copy.h>
 #include <GeomAlgoAPI_MapShapesAndAncestors.h>
 #include <GeomAlgoAPI_ShapeTools.h>
+#include <GeomAlgoAPI_CompoundBuilder.h>
 
 #include <GeomAPI_Edge.h>
 #include <GeomAPI_Pln.h>
 #include <GeomAPI_Pnt.h>
 #include <GeomAPI_Wire.h>
 #include <GeomAPI_WireExplorer.h>
+#include <GeomAPI_ShapeIterator.h>
 
 #include <GEOMImpl_Fillet1d.hxx>
 
@@ -66,14 +68,64 @@ GeomAlgoAPI_Fillet1D::GeomAlgoAPI_Fillet1D(const GeomShapePtr& theBaseWire,
   build(theBaseWire, theFilletVertices, theFilletRadius);
 }
 
-void GeomAlgoAPI_Fillet1D::build(const GeomShapePtr& theBaseWire,
+void GeomAlgoAPI_Fillet1D::build(const GeomShapePtr& theBaseShape,
                                  const ListOfShape&  theFilletVertices,
                                  const double        theRadius)
 {
-  if (!theBaseWire || theFilletVertices.empty() || theRadius < 0.)
+  if (!theBaseShape.get() || theFilletVertices.empty() || theRadius < 0.)
     return;
 
   myFailedVertices.clear();
+  GeomShapePtr aShape;
+
+  if (theBaseShape->isWire())
+    aShape = buildWire(theBaseShape, theFilletVertices, theRadius);
+  else if (theBaseShape->isCompound()) {
+    std::list<GeomShapePtr> aShapes;
+    for (GeomAPI_ShapeIterator it (theBaseShape); it.more(); it.next()) {
+      GeomShapePtr aSubShape = it.current();
+      if (aSubShape->isWire()) {
+        // get only fillet vertices of current wire
+        ListOfShape aFilletVertices;
+        std::set<GeomShapePtr, GeomAPI_Shape::Comparator> aFilletVerticesSet
+          (theFilletVertices.begin(), theFilletVertices.end());
+        for (GeomAPI_WireExplorer anExp(aSubShape->wire()); anExp.more(); anExp.next()) {
+          if (aFilletVerticesSet.find(anExp.currentVertex()) != aFilletVerticesSet.end())
+            aFilletVertices.push_back(anExp.currentVertex());
+        }
+
+        GeomShapePtr aShape_i = buildWire(aSubShape, aFilletVertices, theRadius);
+        if (aShape_i.get() != NULL)
+          aShapes.push_back(aShape_i);
+        else
+          aShapes.push_back(aSubShape);
+      }
+      else {
+        setDone(false);
+        myError = "Input shape for fillet is neither a wire nor a compound of wires";
+        return;
+      }
+    }
+    aShape = GeomAlgoAPI_CompoundBuilder::compound(aShapes);
+    myModified[theBaseShape].push_back(aShape);
+  } else {
+    setDone(false);
+    myError = "Input shape for fillet is neither a wire nor a compound";
+    return;
+  }
+
+  setShape(aShape);
+  setDone(myFailedVertices.empty());
+}
+
+GeomShapePtr GeomAlgoAPI_Fillet1D::buildWire(const GeomShapePtr& theBaseWire,
+                                             const ListOfShape&  theFilletVertices,
+                                             const double        theRadius)
+{
+  std::shared_ptr<GeomAPI_Shape> aShape;
+  if (!theBaseWire.get() || theFilletVertices.empty() || theRadius < 0.)
+    return aShape;
+
   // store all edges of a base wire as modified, because they will be rebuild by ShapeFix
   for (GeomAPI_WireExplorer aWExp(theBaseWire->wire()); aWExp.more(); aWExp.next()) {
     GeomShapePtr aCurrent = aWExp.current();
@@ -103,7 +155,7 @@ void GeomAlgoAPI_Fillet1D::build(const GeomShapePtr& theBaseWire,
 
     GeomPlanePtr aPlane = GeomAlgoAPI_ShapeTools::findPlane(anEdges);
     if (!aPlane)
-      return; // non-planar edges
+      return aShape; // non-planar edges
 
     TopoDS_Edge anEdge1 = TopoDS::Edge(anEdges.front()->impl<TopoDS_Shape>());
     TopoDS_Edge anEdge2 = TopoDS::Edge(anEdges.back()->impl<TopoDS_Shape>());
@@ -148,9 +200,10 @@ void GeomAlgoAPI_Fillet1D::build(const GeomShapePtr& theBaseWire,
       aNewEdges.push_back(aWExp.current());
     for (ListOfShape::iterator anIt = aNewEdges.begin(); anIt != aNewEdges.end(); ++anIt)
       aBuilder.Add(aNewWire, TopoDS::Edge((*anIt)->impl<TopoDS_Shape>()));
-  }
-  for (MapModified::iterator aGenIt = myGenerated.begin(); aGenIt != myGenerated.end(); ++aGenIt) {
-    for (ListOfShape::iterator anIt = aGenIt->second.begin(); anIt != aGenIt->second.end(); ++anIt)
+
+    ListOfShape aNewEdges1;
+    generated(aWExp.currentVertex(), aNewEdges1);
+    for (ListOfShape::iterator anIt = aNewEdges1.begin(); anIt != aNewEdges1.end(); ++anIt)
       aBuilder.Add(aNewWire, TopoDS::Edge((*anIt)->impl<TopoDS_Shape>()));
   }
   // fix the wire connectivity
@@ -161,7 +214,7 @@ void GeomAlgoAPI_Fillet1D::build(const GeomShapePtr& theBaseWire,
   aNewWire = aFixWire.WireAPIMake();
   if (aNewWire.IsNull()) {
     myFailedVertices = theFilletVertices;
-    return;
+    return aShape;
   }
 
   // update the map of modified shapes, because the edges are changed by ShapeFix
@@ -197,12 +250,10 @@ void GeomAlgoAPI_Fillet1D::build(const GeomShapePtr& theBaseWire,
     aNewWire = aReorderedWire;
   }
 
-  std::shared_ptr<GeomAPI_Shape> aShape(new GeomAPI_Shape());
-  aShape->setImpl(new TopoDS_Shape(aNewWire));
-  myModified[theBaseWire].push_back(aShape);
-
-  setShape(aShape);
-  setDone(myFailedVertices.empty());
+  std::shared_ptr<GeomAPI_Shape> aResShape(new GeomAPI_Shape);
+  aResShape->setImpl(new TopoDS_Shape(aNewWire));
+  myModified[theBaseWire].push_back(aResShape);
+  return aResShape;
 }
 
 void GeomAlgoAPI_Fillet1D::generated(const GeomShapePtr theOldShape,
index 702b11398d0135ffabf780ea1df23f8d13d6c865..e046c74c674388d1ac9c910910b316bd4c65a728 100644 (file)
@@ -34,8 +34,8 @@ class GeomAlgoAPI_Fillet1D : public GeomAlgoAPI_MakeShape
 
 public:
   /// Run fillet operation on a set of vertices with fixed radius.
-  /// \param theBaseWire        a changing Wire
-  /// \param theFilletVertices  list of edges the fillet is performed on
+  /// \param theBaseWire        a wire or a compound of wires
+  /// \param theFilletVertices  list of vertices the fillet is performed on
   /// \param theFilletRadius    radius of the fillet
   GEOMALGOAPI_EXPORT GeomAlgoAPI_Fillet1D(const GeomShapePtr& theBaseWire,
                                           const ListOfShape&  theFilletVertices,
@@ -57,14 +57,22 @@ public:
   const ListOfShape& failedVertices() const { return myFailedVertices; }
 
 private:
-  /// Perform 1d-fillet on wire
-  /// \param theBaseWire        a changing wire
-  /// \param theFilletVertices  list of vertices of filler
+  /// Perform 1d-fillet on a wire or a compound of wires
+  /// \param theBaseShape       the base wire or a compound of wires for fillet
+  /// \param theFilletVertices  list of vertices of fillet
   /// \param theRadius          fillet radius
-  void build(const GeomShapePtr& theBaseWire,
+  void build(const GeomShapePtr& theBaseShape,
              const ListOfShape&  theFilletVertices,
              const double        theRadius);
 
+  /// Perform 1d-fillet on wire
+  /// \param theBaseWire        the base wire for fillet
+  /// \param theFilletVertices  list of vertices of fillet
+  /// \param theRadius          fillet radius
+  GeomShapePtr buildWire(const GeomShapePtr& theBaseWire,
+                         const ListOfShape&  theFilletVertices,
+                         const double        theRadius);
+
 private:
   MapModified myGenerated;
   MapModified myModified;
index 886bca14bbb8e7f7417bc70d8987a390865d5b8f..6032c421aaf471469f421c16d637947292a3164d 100644 (file)
@@ -64,7 +64,8 @@ void GeomAlgoAPI_Offset::generated(const GeomShapePtr theOldShape,
 
 GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomPlanePtr& thePlane,
                                        const GeomShapePtr& theEdgeOrWire,
-                                       const double theOffsetValue)
+                                       const double theOffsetValue,
+                                       const GeomAlgoAPI_OffsetJoint theJoint)
 {
   // 1. Make wire from edge, if need
   TopoDS_Wire aWire;
@@ -92,8 +93,14 @@ GeomAlgoAPI_Offset::GeomAlgoAPI_Offset(const GeomPlanePtr& thePlane,
   setImpl(aParal);
   setBuilderType(OCCT_BRepBuilderAPI_MakeShape);
 
+  // Joint type
+  GeomAbs_JoinType aJoin = GeomAbs_Arc; // default mode, corresponding to KeepDistance
+  if (theJoint == GeomAlgoAPI_OffsetJoint::Lines)
+    aJoin = GeomAbs_Intersection;
+  // for GeomAlgoAPI_OffsetJoint::Arcs do the same as for KeepDistance
+
   Standard_Boolean isOpenResult = !aWire.Closed();
-  aParal->Init(aFace, GeomAbs_Arc, isOpenResult);
+  aParal->Init(aFace, aJoin, isOpenResult);
   aParal->Perform(theOffsetValue, 0.);
   if (aParal->IsDone()) {
     TopoDS_Shape anOffset = aParal->Shape();
index 5b0ffc0b36b6bd8ce2d52766733b793bd020e865..a4be309a7a06dbd5481758b0576d06af8d6fc4c1 100644 (file)
 
 class GeomAPI_Pln;
 
+/// \enum GeomAlgoAPI_OffsetJoint
+/// \brief Type of joint of straight edges in 2D offset
+/// \a KeepDistance Keep the distance from initial contour to the resulting one,
+///    creating contour from lines and arcs where it is needed
+/// \a Arcs On connection of straight edges, tangent arcs are created,
+///    except the case of too short edges. Radius of arcs equals to the offset value.
+/// \a Lines No arcs is created on the connection of the resulting lines,
+///    adjacent lines are prolonged to the point of their intersection.
+enum class GeomAlgoAPI_OffsetJoint { KeepDistance, Arcs, Lines };
+
 /// \class GeomAlgoAPI_Offset
 /// \ingroup DataAlgo
 /// \brief Perform 3D offset for the shape
@@ -39,9 +49,12 @@ public:
   /// \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);
+  /// \param[in] theJointType type of joint of straight edges
+  GEOMALGOAPI_EXPORT GeomAlgoAPI_Offset
+    (const std::shared_ptr<GeomAPI_Pln>& thePlane,
+     const GeomShapePtr& theEdgeOrWire,
+     const double theOffsetValue,
+     const GeomAlgoAPI_OffsetJoint theJoint = GeomAlgoAPI_OffsetJoint::KeepDistance);
 
   /// \return the list of shapes generated from the shape \a theShape.
   /// \param[in] theOldShape base shape.
index 927b29e39aae2b0c325b77252484c22c1075698e..55edd27e69fddecec7ac6bc342ad5f3decc2b9da 100644 (file)
@@ -54,7 +54,8 @@ void Model_AttributeIntArray::setSize(const int theSize, bool sendUpdated)
   } else { // reset the old array
     if (theSize) {
       if (theSize != myArray->Length()) { // old data is not kept, a new array is created
-        Handle(TColStd_HArray1OfInteger) aNewArray = new TColStd_HArray1OfInteger(0, theSize - 1);
+        Handle(TColStd_HArray1OfInteger) aNewArray =
+          new TColStd_HArray1OfInteger(0, theSize - 1, 0);
         myArray->ChangeArray(aNewArray);
         if (sendUpdated)
           owner()->data()->sendAttributeUpdated(this);
index d23d153ffaa97e94cf4bb4eda0cc9111888537b6..3be3b0c7d2af5e39e7efa1b212f1ac0675e3f9f2 100644 (file)
@@ -34,13 +34,15 @@ SketchAPI_Offset::SketchAPI_Offset (const std::shared_ptr<ModelAPI_Feature> & th
 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)
+                                    const bool theIsReversed,
+                                    const std::string & theJointType)
   : ModelHighAPI_Interface(theFeature)
 {
   if (initialize()) {
     fillAttribute(theObjects, edgesList());
     fillAttribute(theOffsetValue, value());
     fillAttribute(theIsReversed, reversed());
+    fillAttribute(theJointType, joint());
 
     execute();
   }
@@ -72,6 +74,7 @@ void SketchAPI_Offset::dump (ModelHighAPI_Dumper& theDumper) const
   AttributeRefListPtr aOffsetObjects = edgesList();
   AttributeDoublePtr aValue = value();
   AttributeBooleanPtr aReversed = reversed();
+  AttributeStringPtr aJoint = joint();
 
   // Check all attributes are already dumped. If not, store the feature as postponed.
   if (!theDumper.isDumped(aOffsetObjects)) {
@@ -80,7 +83,7 @@ void SketchAPI_Offset::dump (ModelHighAPI_Dumper& theDumper) const
   }
 
   theDumper << aBase << " = " << aSketchName << ".addOffset(" << aOffsetObjects << ", "
-            << aValue << ", " << aReversed << ")" << std::endl;
+            << aValue << ", " << aReversed  << ", " << aJoint << ")" << std::endl;
 
   // Dump variables for a list of created features
   theDumper << "[";
index 0446c792d6486406aebcbf36559f8b7f064c75b3..6e201bd25115fdc930b31014e226187f056fa29d 100644 (file)
@@ -50,12 +50,13 @@ public:
   SketchAPI_Offset(const std::shared_ptr<ModelAPI_Feature> & theFeature,
                    const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
                    const ModelHighAPI_Double & theOffsetValue,
-                   bool theIsReversed);
+                   const bool theIsReversed = false,
+                   const std::string & theJointType = SketchPlugin_Offset::JOINT_KEEP_DISTANCE());
   /// Destructor
   SKETCHAPI_EXPORT
   virtual ~SketchAPI_Offset();
 
-  INTERFACE_3(SketchPlugin_Offset::ID(),
+  INTERFACE_4(SketchPlugin_Offset::ID(),
 
               edgesList, SketchPlugin_Offset::EDGES_ID(),
               ModelAPI_AttributeRefList, /** Offset edges list */,
@@ -64,7 +65,10 @@ public:
               ModelAPI_AttributeDouble, /** Value */,
 
               reversed, SketchPlugin_Offset::REVERSED_ID(),
-              ModelAPI_AttributeBoolean, /** Negative value */
+              ModelAPI_AttributeBoolean, /** Negative value */,
+
+              joint, SketchPlugin_Offset::JOINT_ID(),
+              ModelAPI_AttributeString, /** Joint type */
               )
 
   /// List of created objects
index 97ba31d545e2a2419d71b3d7f74618e48a6d2fc9..7ff5622b6b42dab83792dcfa44f717c0327d3bf4 100644 (file)
@@ -930,11 +930,12 @@ std::shared_ptr<SketchAPI_Mirror> SketchAPI_Sketch::addMirror(
 std::shared_ptr<SketchAPI_Offset> SketchAPI_Sketch::addOffset(
     const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
     const ModelHighAPI_Double & theValue,
-    const bool theReversed)
+    const bool theReversed,
+    const std::string & theJointType)
 {
   std::shared_ptr<ModelAPI_Feature> aFeature =
     compositeFeature()->addFeature(SketchPlugin_Offset::ID());
-  return OffsetPtr(new SketchAPI_Offset(aFeature, theObjects, theValue, theReversed));
+  return OffsetPtr(new SketchAPI_Offset(aFeature, theObjects, theValue, theReversed, theJointType));
 }
 
 //--------------------------------------------------------------------------------------
index 51affdef35f3962af6a7c38793fc8d0310bae7dd..498a124e62a297c7545a658157ca9c34bc4272f5 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <SketchPlugin_Sketch.h>
 #include <SketchPlugin_SketchEntity.h>
+#include <SketchPlugin_Offset.h>
 
 #include <ModelHighAPI_Double.h>
 #include <ModelHighAPI_Interface.h>
@@ -378,7 +379,8 @@ public:
   std::shared_ptr<SketchAPI_Offset> addOffset(
       const std::list<std::shared_ptr<ModelAPI_Object> > & theObjects,
       const ModelHighAPI_Double & theValue,
-      const bool theReversed);
+      const bool theReversed = false,
+      const std::string & theJointType = SketchPlugin_Offset::JOINT_KEEP_DISTANCE());
 
   /// Add translation
   SKETCHAPI_EXPORT
index 9f0ee1a235e73f320745356a34d0b67798b6e01e..27d957a51f76f4d7f1c5a27dae69b8fde2ed10d7 100644 (file)
 #include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_AttributeIntArray.h>
 #include <ModelAPI_AttributeRefList.h>
+#include <ModelAPI_AttributeString.h>
 #include <ModelAPI_Events.h>
 #include <ModelAPI_ResultConstruction.h>
 #include <ModelAPI_Tools.h>
 #include <ModelAPI_Validator.h>
 
 #include <GeomAlgoAPI_MakeShapeList.h>
+#include <GeomAlgoAPI_MapShapesAndAncestors.h>
 #include <GeomAlgoAPI_Offset.h>
 #include <GeomAlgoAPI_ShapeTools.h>
 #include <GeomAlgoAPI_WireBuilder.h>
+#include <GeomAlgoAPI_Fillet1D.h>
+#include <GeomAlgoAPI_Tools.h>
 
 #include <GeomAPI_BSpline.h>
 #include <GeomAPI_Circ.h>
@@ -61,6 +65,8 @@
 #include <GeomDataAPI_Point2D.h>
 #include <GeomDataAPI_Point2DArray.h>
 
+#include <math.h>
+
 static const double tolerance = 1.e-7;
 
 SketchPlugin_Offset::SketchPlugin_Offset()
@@ -86,6 +92,11 @@ void SketchPlugin_Offset::initAttributes()
       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_B());
   ModelAPI_Session::get()->validators()->
       registerNotObligatory(getKind(), SketchPlugin_Constraint::ENTITY_C());
+
+  AttributeStringPtr aJointAttr = std::dynamic_pointer_cast<ModelAPI_AttributeString>
+    (data()->addAttribute(JOINT_ID(), ModelAPI_AttributeString::typeId()));
+  if (!aJointAttr->isInitialized())
+    aJointAttr->setValue(JOINT_KEEP_DISTANCE());
 }
 
 void SketchPlugin_Offset::execute()
@@ -93,6 +104,20 @@ void SketchPlugin_Offset::execute()
   SketchPlugin_Sketch* aSketch = sketch();
   if (!aSketch) return;
 
+  // 0. Joint type
+  AttributeStringPtr aJointAttr = string(JOINT_ID());
+  std::string aType = JOINT_KEEP_DISTANCE();
+  if (aJointAttr->isInitialized())
+    aType = aJointAttr->value();
+
+  GeomAlgoAPI_OffsetJoint aJoint;
+  if (aType == JOINT_ARCS())
+    aJoint = GeomAlgoAPI_OffsetJoint::Arcs;
+  else if (aType == JOINT_LINES())
+    aJoint = GeomAlgoAPI_OffsetJoint::Lines;
+  else // Default mode
+    aJoint = GeomAlgoAPI_OffsetJoint::KeepDistance;
+
   // 1. Sketch plane
   std::shared_ptr<GeomAPI_Pln> aPlane = aSketch->plane();
 
@@ -189,14 +214,21 @@ void SketchPlugin_Offset::execute()
       }
 
       // 5.d. Make offset for the wire
-      std::shared_ptr<GeomAlgoAPI_Offset> anOffsetShape(
-          new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign));
+      std::shared_ptr<GeomAlgoAPI_Offset> anOffsetShape
+        (new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign, aJoint));
 
       if (anOffsetShape->isDone()) {
-        std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList(new GeomAlgoAPI_MakeShapeList);
-        aMakeList->appendAlgo(aWireBuilder);
-        aMakeList->appendAlgo(anOffsetShape);
-        anOffsetAlgos.push_back(aMakeList);
+        if (aJoint == GeomAlgoAPI_OffsetJoint::Arcs) {
+          // For Arcs joint make fillet at all straight edges intersections
+          // of the wire, resulting from GeomAlgoAPI_Offset algorithm
+          makeFillet(fabs(aValue), aWireBuilder, anOffsetShape, anOffsetAlgos);
+        }
+        else {
+          std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList (new GeomAlgoAPI_MakeShapeList);
+          aMakeList->appendAlgo(aWireBuilder);
+          aMakeList->appendAlgo(anOffsetShape);
+          anOffsetAlgos.push_back(aMakeList);
+        }
       }
       else {
         setError("Offset algorithm failed");
@@ -753,7 +785,6 @@ bool SketchPlugin_Offset::findWires()
       }
     }
   }
-  // TODO: hilight selection in the viewer
 
   data()->blockSendAttributeUpdated(aWasBlocked);
   return true;
@@ -769,3 +800,111 @@ AISObjectPtr SketchPlugin_Offset::getAISObject(AISObjectPtr thePrevious)
     thePrevious);
   return anAIS;
 }
+
+
+void SketchPlugin_Offset::makeFillet
+     (const double theValue,
+      const std::shared_ptr<GeomAlgoAPI_WireBuilder>& theWireBuilder,
+      const std::shared_ptr<GeomAlgoAPI_Offset>& theOffsetShape,
+      ListOfMakeShape& theOffsetAlgos)
+{
+  std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeList (new GeomAlgoAPI_MakeShapeList);
+  aMakeList->appendAlgo(theWireBuilder);
+  aMakeList->appendAlgo(theOffsetShape);
+
+  bool isOK = true;
+
+  GeomShapePtr aResWire = theOffsetShape->shape();
+  GeomAlgoAPI_MapShapesAndAncestors aMapVE
+    (aResWire, GeomAPI_Shape::VERTEX, GeomAPI_Shape::EDGE);
+  const MapShapeToShapes& aSubshapes = aMapVE.map();
+
+  // find vertices for fillet
+  std::set<GeomShapePtr, GeomAPI_Shape::Comparator> aFilletVertices;
+  for (MapShapeToShapes::const_iterator anIt = aSubshapes.begin();
+       anIt != aSubshapes.end(); ++anIt) {
+    // vertex should have 2 adjacent edges
+    if (anIt->second.size() != 2)
+      continue;
+
+    // both edges should be linear
+    ListOfShape anEdges;
+    anEdges.insert(anEdges.end(), anIt->second.begin(), anIt->second.end());
+    GeomEdgePtr anEdge1 (new GeomAPI_Edge(anEdges.front()));
+    GeomEdgePtr anEdge2 (new GeomAPI_Edge(anEdges.back()));
+    if (!anEdge1->isLine() || !anEdge2->isLine())
+      continue;
+
+    // skip vertices, which smoothly connect adjacent edges
+    GeomVertexPtr aSharedVertex(new GeomAPI_Vertex(anIt->first));
+    if (GeomAlgoAPI_ShapeTools::isTangent(anEdge1, anEdge2, aSharedVertex))
+      continue;
+
+    aFilletVertices.insert(anIt->first);
+  }
+
+  if (!aFilletVertices.empty()) {
+    isOK = false; // the wire needs correction
+    ListOfShape aVerticesList (aFilletVertices.begin(), aFilletVertices.end());
+
+    // Fillet1D on all linear edges intersections
+    std::shared_ptr<GeomAlgoAPI_Fillet1D> aFilletBuilder
+      (new GeomAlgoAPI_Fillet1D(aResWire, aVerticesList, theValue));
+
+    std::string anError;
+    if (!GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed
+        (aFilletBuilder, getKind(), anError)) {
+      aMakeList->appendAlgo(aFilletBuilder);
+      isOK = true;
+    }
+    else {
+      ListOfShape aFailedVertices = aFilletBuilder->failedVertices();
+      if (aFailedVertices.size() != 0) {
+        // Exclude failed vertices and also vertices, joined
+        // with failed by one edge, and run algorithm once again
+        ListOfShape::iterator itVertices = aFailedVertices.begin();
+        for (; itVertices != aFailedVertices.end(); itVertices++) {
+          GeomShapePtr aFailedVertex = *itVertices;
+          aFilletVertices.erase(aFailedVertex);
+          // remove also neighbour vertices
+          MapShapeToShapes::const_iterator anIt = aSubshapes.find(aFailedVertex);
+          if (anIt != aSubshapes.end()) { // should be always true
+            ListOfShape anEdges;
+            anEdges.insert(anEdges.end(), anIt->second.begin(), anIt->second.end());
+            GeomEdgePtr anEdge1 (new GeomAPI_Edge(anEdges.front()));
+            GeomEdgePtr anEdge2 (new GeomAPI_Edge(anEdges.back()));
+            GeomVertexPtr V1, V2;
+            anEdge1->vertices(V1, V2);
+            if (V1->isEqual(aFailedVertex)) V1 = V2;
+            aFilletVertices.erase(V1);
+            anEdge2->vertices(V1, V2);
+            if (V1->isEqual(aFailedVertex)) V1 = V2;
+            aFilletVertices.erase(V1);
+          }
+        }
+        if (aFilletVertices.size() == 0) {
+          // there are no suitable vertices for fillet
+          isOK = true;
+        }
+        else {
+          // Fillet1D one more try
+          ListOfShape aVerticesList1 (aFilletVertices.begin(), aFilletVertices.end());
+
+          std::shared_ptr<GeomAlgoAPI_Fillet1D> aFilletBuilder1
+            (new GeomAlgoAPI_Fillet1D(aResWire, aVerticesList1, theValue));
+
+          if (!GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed
+              (aFilletBuilder1, getKind(), anError)) {
+            aMakeList->appendAlgo(aFilletBuilder1);
+            isOK = true;
+          }
+        }
+      }
+    }
+  }
+
+  if (isOK)
+    theOffsetAlgos.push_back(aMakeList);
+  else
+    setError("Offset algorithm failed");
+}
index fbfd5c39f00c92cc6839998e81a848b4e3f40a08..fd43ff721144f3aea76b40746ed4a9059ddb40a8 100644 (file)
@@ -28,6 +28,8 @@
 #include <GeomAPI_Edge.h>
 
 class GeomAlgoAPI_MakeShape;
+class GeomAlgoAPI_Offset;
+class GeomAlgoAPI_WireBuilder;
 
 /**\class SketchPlugin_Offset
  * \ingroup Plugins
@@ -50,6 +52,34 @@ public:
     return MY_KIND;
   }
 
+  /// Type of joint
+  inline static const std::string& JOINT_ID()
+  {
+    static const std::string ID("offset_joint");
+    return ID;
+  }
+
+  /// Keep distance joint (add arcs where needed)
+  inline static const std::string& JOINT_KEEP_DISTANCE()
+  {
+    static const std::string ID("KeepDistance");
+    return ID;
+  }
+
+  /// Arcs joint (make fillets on all straight lines intersections)
+  inline static const std::string& JOINT_ARCS()
+  {
+    static const std::string ID("Arcs");
+    return ID;
+  }
+
+  /// Lines joint (do not add new arcs, prolongate and intersect adjacent lines)
+  inline static const std::string& JOINT_LINES()
+  {
+    static const std::string ID("Lines");
+    return ID;
+  }
+
   /// list of offset edges
   inline static const std::string& EDGES_ID()
   {
@@ -140,6 +170,11 @@ private:
                        std::set<FeaturePtr>& theProcessedEdgesSet,
                        std::list<FeaturePtr>& theChain,
                        const bool isPrepend = false);
+
+  void makeFillet (const double theValue,
+                   const std::shared_ptr<GeomAlgoAPI_WireBuilder>&,
+                   const std::shared_ptr<GeomAlgoAPI_Offset>&,
+                   std::list< std::shared_ptr<GeomAlgoAPI_MakeShape> >& theOffsetAlgos);
 };
 
 #endif
diff --git a/src/SketchPlugin/Test/TestOffset3.py b/src/SketchPlugin/Test/TestOffset3.py
new file mode 100644 (file)
index 0000000..f5e1f42
--- /dev/null
@@ -0,0 +1,106 @@
+# Copyright (C) 2020-2021  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+from salome.shaper import model
+from SketchAPI import *
+
+model.begin()
+partSet = model.moduleDocument()
+
+### Create Sketch
+Sketch_1 = model.addSketch(partSet, model.defaultPlane("XOY"))
+
+### Create SketchLine
+SketchLine_1 = Sketch_1.addLine(0, 0, 0, 130)
+
+### Create SketchProjection
+SketchProjection_1 = Sketch_1.addProjection(model.selection("VERTEX", "Origin"), False)
+SketchPoint_1 = SketchProjection_1.createdFeature()
+Sketch_1.setCoincident(SketchLine_1.startPoint(), SketchAPI_Point(SketchPoint_1).coordinates())
+Sketch_1.setVertical(SketchLine_1.result())
+Sketch_1.setLength(SketchLine_1.result(), 130)
+
+### Create SketchLine
+SketchLine_2 = Sketch_1.addLine(0, 130, 52.63824701112046, 101.2039073554021)
+Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint())
+Sketch_1.setLength(SketchLine_2.result(), 60)
+
+### Create SketchLine
+SketchLine_3 = Sketch_1.addLine(52.63824701112046, 101.2039073554021, 199.7207674554361, 130.6442229064876)
+Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchLine_3.startPoint())
+Sketch_1.setLength(SketchLine_3.result(), 150)
+
+### Create SketchConstraintAngle
+Sketch_1.setAngle(SketchLine_3.result(), SketchLine_2.result(), 140, type = "Direct")
+
+### Create SketchLine
+SketchLine_4 = Sketch_1.addLine(158.0815949372134, 7.493180231252017, 199.7207674554361, 130.6442229064876)
+Sketch_1.setCoincident(SketchLine_3.endPoint(), SketchLine_4.endPoint())
+Sketch_1.setLength(SketchLine_4.result(), 130)
+
+### Create SketchConstraintAngle
+Sketch_1.setAngle(SketchLine_3.result(), SketchLine_4.result(), 60, type = "Direct")
+
+### Create SketchLine
+SketchLine_5 = Sketch_1.addLine(158.0815949372134, 7.493180231252017, 106.6596776583922, 54.98830022657437)
+Sketch_1.setCoincident(SketchLine_4.startPoint(), SketchLine_5.startPoint())
+Sketch_1.setLength(SketchLine_5.result(), 70)
+
+### Create SketchLine
+SketchLine_6 = Sketch_1.addLine(106.6596776583922, 54.98830022657437, 0, 0)
+Sketch_1.setCoincident(SketchLine_5.endPoint(), SketchLine_6.startPoint())
+Sketch_1.setCoincident(SketchLine_6.endPoint(), SketchLine_1.startPoint())
+Sketch_1.setLength(SketchLine_6.result(), 120)
+
+### Create SketchConstraintAngle
+Sketch_1.setAngle(SketchLine_6.result(), SketchLine_5.result(), 110, type = "Direct")
+
+### SketchOffset objects
+SketchOffset_objects = [SketchLine_1.result(), SketchLine_2.result(), SketchLine_3.result(), SketchLine_4.result(), SketchLine_5.result(), SketchLine_6.result()]
+
+### 1. "KeepDistance" mode
+
+SketchOffset_1 = Sketch_1.addOffset(SketchOffset_objects, 8, False, "KeepDistance") # outside
+SketchOffset_2 = Sketch_1.addOffset(SketchOffset_objects, 8, True, "KeepDistance") # inside
+model.do()
+
+assert(len(SketchOffset_1.offset()) == 10)
+assert(len(SketchOffset_2.offset()) == 8)
+
+### 2. "Arcs" mode
+
+SketchOffset_3 = Sketch_1.addOffset(SketchOffset_objects, 16, False, "Arcs") # outside
+SketchOffset_4 = Sketch_1.addOffset(SketchOffset_objects, 16, True, "Arcs") # inside
+model.do()
+
+assert(len(SketchOffset_3.offset()) == 12)
+assert(len(SketchOffset_4.offset()) == 12)
+
+### 3. "Lines" mode
+
+SketchOffset_5 = Sketch_1.addOffset(SketchOffset_objects, 24, False, "Lines") # outside
+SketchOffset_6 = Sketch_1.addOffset(SketchOffset_objects, 24, True, "Lines") # inside
+model.do()
+
+assert(len(SketchOffset_5.offset()) == 6)
+assert(len(SketchOffset_6.offset()) == 6)
+
+model.end()
+
+assert(model.checkPythonDump())
index 881db9a4004433342f6a097a7e6dec3e0be16acf..79e8ae9e1b97c9ae1eed660f64700364e1f8a73b 100644 (file)
@@ -16,7 +16,18 @@ 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()]
+
+### KeepDistance (default) mode, not reversed (outside)
 SketchOffset_1 = Sketch_1.addOffset(SketchOffset_1_objects, 10.0, False)
 
+### KeepDistance mode, reversed (inside)
+SketchOffset_2 = Sketch_1.addOffset(SketchOffset_1_objects, 25.0, True, "KeepDistance")
+
+### Arcs mode, reversed (inside)
+SketchOffset_3 = Sketch_1.addOffset(SketchOffset_1_objects, 15.0, True, "Arcs")
+
+### Lines mode, not reversed (outside)
+SketchOffset_4 = Sketch_1.addOffset(SketchOffset_1_objects, 20.0, False, "Lines")
+
 model.do()
 model.end()
index d34ad6c3c35e1f7692de854f1b8ceb0c8bffabee..de9741e548eaf8e20bb5d2bb24bb127a467e044a 100644 (file)
Binary files a/src/SketchPlugin/doc/images/Offset_panel.png and b/src/SketchPlugin/doc/images/Offset_panel.png differ
index ce42254dfbce3f71bb7495628d8f1f5266866c51..65af062c925362fd25d512e558383fab38f9de48 100644 (file)
@@ -4,10 +4,24 @@ 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.
 
+Edges junctions are filled in accordance with selected offset mode.
+The following modes are implemented:
+  - **Keep distance** mode, enabled by default. It keeps the distance from initial
+    contour to the resulting one, creating additional arcs where it is needed.
+  - **Arcs mode**, where on connection of straight segments, tangent arcs are created.
+    Only connection of straight lines is supported; with other types of curves,
+    offset will work as in **Keep distance** mode. Radius of arcs equals to the offset value.
+    These arcs cut off the initially connected lines, so, if in result at least one
+    such line becomes invalid (removed from the result), the arc is not created
+    in this connection, the line is created in this point as in the **Lines** mode.
+  - **Lines** mode, where no arcs is created on the connection of the resulting lines.
+    Adjacent lines are prolonged to the point of their intersection. If the offset
+    arguments are not straigh lines (arcs, bspline, etc), the offset will work
+    as in the **Keep distance** mode in these locations.
+
 To create an Offset in the active Sketch:
 
 #. select in the Main Menu *Sketch - > Offset* item  or
@@ -23,6 +37,7 @@ Property panel:
 
 Input fields:
 
+- Offset mode can be **Keep distance**, **Arcs** or **Lines**
 - **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).
@@ -35,11 +50,12 @@ Button:
 
 **TUI Command**:
 
-.. py:function:: Sketch_1.addOffset(Objects, Distance, isReversed)
+.. py:function:: Sketch_1.addOffset(Objects, Distance, isReversed, Mode)
 
     :param list: A list of objects.
     :param real: An offset distance.
     :param boolean: Reversed flag.
+    :param string: Offset mode. Can be "KeepDistance", "Arcs" or "Lines".
     :return: Result object.
 
 Result
diff --git a/src/SketchPlugin/icons/offset_arcs_32x32.png b/src/SketchPlugin/icons/offset_arcs_32x32.png
new file mode 100644 (file)
index 0000000..046c60a
Binary files /dev/null and b/src/SketchPlugin/icons/offset_arcs_32x32.png differ
diff --git a/src/SketchPlugin/icons/offset_keep_distance_32x32.png b/src/SketchPlugin/icons/offset_keep_distance_32x32.png
new file mode 100644 (file)
index 0000000..ae7dd5b
Binary files /dev/null and b/src/SketchPlugin/icons/offset_keep_distance_32x32.png differ
diff --git a/src/SketchPlugin/icons/offset_lines_32x32.png b/src/SketchPlugin/icons/offset_lines_32x32.png
new file mode 100644 (file)
index 0000000..486f6e9
Binary files /dev/null and b/src/SketchPlugin/icons/offset_lines_32x32.png differ
index 824e6281b2c9b60f4145dc74efe6a0fd50fbb68b..86b234c51909ff957f29cf0a18559ff0a77ab21a 100644 (file)
                tooltip="Offset a curve to a distance"
                icon="icons/Sketch/offset.png"
                helpfile="offsetFeature.html">
+        <toolbox id="offset_joint">
+           <box id="KeepDistance"
+               icon="icons/Sketch/offset_keep_distance_32x32.png"
+               title="Keep distance"/>
+           <box id="Arcs"
+               icon="icons/Sketch/offset_arcs_32x32.png"
+               title="Arcs"/>
+           <box id="Lines"
+               icon="icons/Sketch/offset_lines_32x32.png"
+               title="Lines"/>
+        </toolbox>
         <sketch_multi_selector id="segments"
                                label="Edges"
                                tooltip="Select edges to offset"
index cff2187723e6073bb04f056fe45ed2f533eb54ee..0c22d8d4c09a4e10806d6a0c9e9fc8dc032d6e0c 100644 (file)
@@ -171,6 +171,7 @@ SET(TEST_NAMES
   TestMultiTranslation.py
   TestOffset1.py
   TestOffset2.py
+  TestOffset3.py
   TestPresentation.py
   TestProjection.py
   TestProjectionBSpline.py