From 38a602debeae7064ef49610717250620f8b01447 Mon Sep 17 00:00:00 2001 From: jfa Date: Wed, 11 May 2022 13:57:50 +0300 Subject: [PATCH] bos #29476 Offset in sketch --- src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp | 77 +++++++-- src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h | 20 ++- src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp | 11 +- src/GeomAlgoAPI/GeomAlgoAPI_Offset.h | 19 ++- src/Model/Model_AttributeIntArray.cpp | 3 +- src/SketchAPI/SketchAPI_Offset.cpp | 7 +- src/SketchAPI/SketchAPI_Offset.h | 10 +- src/SketchAPI/SketchAPI_Sketch.cpp | 5 +- src/SketchAPI/SketchAPI_Sketch.h | 4 +- src/SketchPlugin/SketchPlugin_Offset.cpp | 153 +++++++++++++++++- src/SketchPlugin/SketchPlugin_Offset.h | 35 ++++ src/SketchPlugin/Test/TestOffset3.py | 106 ++++++++++++ src/SketchPlugin/doc/examples/offset.py | 11 ++ src/SketchPlugin/doc/images/Offset_panel.png | Bin 11253 -> 36402 bytes src/SketchPlugin/doc/offsetFeature.rst | 20 ++- src/SketchPlugin/icons/offset_arcs_32x32.png | Bin 0 -> 356 bytes .../icons/offset_keep_distance_32x32.png | Bin 0 -> 303 bytes src/SketchPlugin/icons/offset_lines_32x32.png | Bin 0 -> 225 bytes src/SketchPlugin/plugin-Sketch.xml | 11 ++ src/SketchPlugin/tests.set | 1 + 20 files changed, 451 insertions(+), 42 deletions(-) create mode 100644 src/SketchPlugin/Test/TestOffset3.py create mode 100644 src/SketchPlugin/icons/offset_arcs_32x32.png create mode 100644 src/SketchPlugin/icons/offset_keep_distance_32x32.png create mode 100644 src/SketchPlugin/icons/offset_lines_32x32.png diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp index 34673a7e9..6c6c9ab95 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.cpp @@ -22,12 +22,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include @@ -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 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 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 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_Edge anEdge2 = TopoDS::Edge(anEdges.back()->impl()); @@ -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())); - } - 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())); } // 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 aShape(new GeomAPI_Shape()); - aShape->setImpl(new TopoDS_Shape(aNewWire)); - myModified[theBaseWire].push_back(aShape); - - setShape(aShape); - setDone(myFailedVertices.empty()); + std::shared_ptr aResShape(new GeomAPI_Shape); + aResShape->setImpl(new TopoDS_Shape(aNewWire)); + myModified[theBaseWire].push_back(aResShape); + return aResShape; } void GeomAlgoAPI_Fillet1D::generated(const GeomShapePtr theOldShape, diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h index 702b11398..e046c74c6 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Fillet1D.h @@ -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; diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp index 886bca14b..6032c421a 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp @@ -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(); diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h index 5b0ffc0b3..a4be309a7 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h @@ -25,6 +25,16 @@ 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& thePlane, - const GeomShapePtr& theEdgeOrWire, - const double theOffsetValue); + /// \param[in] theJointType type of joint of straight edges + GEOMALGOAPI_EXPORT GeomAlgoAPI_Offset + (const std::shared_ptr& 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. diff --git a/src/Model/Model_AttributeIntArray.cpp b/src/Model/Model_AttributeIntArray.cpp index 927b29e39..55edd27e6 100644 --- a/src/Model/Model_AttributeIntArray.cpp +++ b/src/Model/Model_AttributeIntArray.cpp @@ -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); diff --git a/src/SketchAPI/SketchAPI_Offset.cpp b/src/SketchAPI/SketchAPI_Offset.cpp index d23d153ff..3be3b0c7d 100644 --- a/src/SketchAPI/SketchAPI_Offset.cpp +++ b/src/SketchAPI/SketchAPI_Offset.cpp @@ -34,13 +34,15 @@ SketchAPI_Offset::SketchAPI_Offset (const std::shared_ptr & th SketchAPI_Offset::SketchAPI_Offset (const std::shared_ptr & theFeature, const std::list > & 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 << "["; diff --git a/src/SketchAPI/SketchAPI_Offset.h b/src/SketchAPI/SketchAPI_Offset.h index 0446c792d..6e201bd25 100644 --- a/src/SketchAPI/SketchAPI_Offset.h +++ b/src/SketchAPI/SketchAPI_Offset.h @@ -50,12 +50,13 @@ public: SketchAPI_Offset(const std::shared_ptr & theFeature, const std::list > & 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 diff --git a/src/SketchAPI/SketchAPI_Sketch.cpp b/src/SketchAPI/SketchAPI_Sketch.cpp index 97ba31d54..7ff5622b6 100644 --- a/src/SketchAPI/SketchAPI_Sketch.cpp +++ b/src/SketchAPI/SketchAPI_Sketch.cpp @@ -930,11 +930,12 @@ std::shared_ptr SketchAPI_Sketch::addMirror( std::shared_ptr SketchAPI_Sketch::addOffset( const std::list > & theObjects, const ModelHighAPI_Double & theValue, - const bool theReversed) + const bool theReversed, + const std::string & theJointType) { std::shared_ptr 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)); } //-------------------------------------------------------------------------------------- diff --git a/src/SketchAPI/SketchAPI_Sketch.h b/src/SketchAPI/SketchAPI_Sketch.h index 51affdef3..498a124e6 100644 --- a/src/SketchAPI/SketchAPI_Sketch.h +++ b/src/SketchAPI/SketchAPI_Sketch.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -378,7 +379,8 @@ public: std::shared_ptr addOffset( const std::list > & 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 diff --git a/src/SketchPlugin/SketchPlugin_Offset.cpp b/src/SketchPlugin/SketchPlugin_Offset.cpp index 9f0ee1a23..27d957a51 100644 --- a/src/SketchPlugin/SketchPlugin_Offset.cpp +++ b/src/SketchPlugin/SketchPlugin_Offset.cpp @@ -40,15 +40,19 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include +#include +#include #include #include @@ -61,6 +65,8 @@ #include #include +#include + 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 + (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 aPlane = aSketch->plane(); @@ -189,14 +214,21 @@ void SketchPlugin_Offset::execute() } // 5.d. Make offset for the wire - std::shared_ptr anOffsetShape( - new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign)); + std::shared_ptr anOffsetShape + (new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign, aJoint)); if (anOffsetShape->isDone()) { - std::shared_ptr 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 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& theWireBuilder, + const std::shared_ptr& theOffsetShape, + ListOfMakeShape& theOffsetAlgos) +{ + std::shared_ptr 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 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 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 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"); +} diff --git a/src/SketchPlugin/SketchPlugin_Offset.h b/src/SketchPlugin/SketchPlugin_Offset.h index fbfd5c39f..fd43ff721 100644 --- a/src/SketchPlugin/SketchPlugin_Offset.h +++ b/src/SketchPlugin/SketchPlugin_Offset.h @@ -28,6 +28,8 @@ #include 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& theProcessedEdgesSet, std::list& theChain, const bool isPrepend = false); + + void makeFillet (const double theValue, + const std::shared_ptr&, + const std::shared_ptr&, + std::list< std::shared_ptr >& theOffsetAlgos); }; #endif diff --git a/src/SketchPlugin/Test/TestOffset3.py b/src/SketchPlugin/Test/TestOffset3.py new file mode 100644 index 000000000..f5e1f422d --- /dev/null +++ b/src/SketchPlugin/Test/TestOffset3.py @@ -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()) diff --git a/src/SketchPlugin/doc/examples/offset.py b/src/SketchPlugin/doc/examples/offset.py index 881db9a40..79e8ae9e1 100644 --- a/src/SketchPlugin/doc/examples/offset.py +++ b/src/SketchPlugin/doc/examples/offset.py @@ -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() diff --git a/src/SketchPlugin/doc/images/Offset_panel.png b/src/SketchPlugin/doc/images/Offset_panel.png index d34ad6c3c35e1f7692de854f1b8ceb0c8bffabee..de9741e548eaf8e20bb5d2bb24bb127a467e044a 100644 GIT binary patch literal 36402 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 Bd=CHs literal 11253 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 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 index 0000000000000000000000000000000000000000..046c60a5713acb1aa3111a7039b607740f62a373 GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXprVhSE{-7)t#7X`^m1_&Irj0s zPV|ulY=ZF)H7v~+mmGAzp!P#S?O@K92p{Pjy8}rE98In@4bnQZ`j2jiDWACda8IJ0 z#Z!ZVf5qo!{>;d75}5hewu0ka(^)C;D890$?giX^-OgtY49xAwyJz91${=)p z$1%^&s$$VMY~TOZG=1HB_(bf6-Lsd-9gvC>UcLFJl5)cl>9Aiv4ew|EU$pS9&b3Ci z5BnLjq)u}PWd+?)JC=$YBH z`}>-t-&|*@I22~u;Cd{GM`5{?1M>>LfY|xWH%lZO`m^T%JgTe~DWM4fN+*v5 literal 0 HcmV?d00001 diff --git a/src/SketchPlugin/icons/offset_keep_distance_32x32.png b/src/SketchPlugin/icons/offset_keep_distance_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..ae7dd5bbea7664729fe89a702db42534de6ceee6 GIT binary patch literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXprSpVE{-7)t#7X?7BLx$uszT} zv#Tp8@F1@R6I0}Q!4==dPxy15tv9#A$L8a8dmpat#XySUo2{nFc4K>xR&ex zpY?jBiSKV2*^H~(o@ARpl{IkS<9HG8CfTi8#eeG7f=}uO2N;-nfT9zgvnj<2SSifT R6$0AK;OXk;vd$@?2>^>`PJ;jd literal 0 HcmV?d00001 diff --git a/src/SketchPlugin/plugin-Sketch.xml b/src/SketchPlugin/plugin-Sketch.xml index 824e6281b..86b234c51 100644 --- a/src/SketchPlugin/plugin-Sketch.xml +++ b/src/SketchPlugin/plugin-Sketch.xml @@ -1020,6 +1020,17 @@ tooltip="Offset a curve to a distance" icon="icons/Sketch/offset.png" helpfile="offsetFeature.html"> + + + + +