From 747178d21dce4fb034ff0e84c1280406ab64d566 Mon Sep 17 00:00:00 2001 From: Artem Zhidkov Date: Mon, 25 May 2020 15:05:31 +0300 Subject: [PATCH] Task #3231: Sketcher Offset of a curve * Implement the feature for the offset operation. * Improve searching of coincidences in context of B-spline curve * Decline offset if duplicated entities are selected. --- src/GeomAPI/GeomAPI_BSpline.cpp | 21 +- src/GeomAPI/GeomAPI_BSpline.h | 12 +- src/GeomAPI/GeomAPI_Edge.cpp | 8 +- src/GeomAlgoAPI/GeomAlgoAPI.i | 1 + src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp | 50 ++ src/GeomAlgoAPI/GeomAlgoAPI_Offset.h | 11 + src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.cpp | 116 ++- src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.h | 12 +- src/Model/Model_AttributeIntArray.cpp | 2 +- src/SketchAPI/CMakeLists.txt | 2 + src/SketchAPI/SketchAPI.i | 2 + src/SketchAPI/SketchAPI_Offset.cpp | 105 +++ src/SketchAPI/SketchAPI_Offset.h | 83 ++ src/SketchAPI/SketchAPI_Sketch.cpp | 13 + src/SketchAPI/SketchAPI_Sketch.h | 8 + src/SketchAPI/SketchAPI_swig.h | 1 + src/SketchPlugin/CMakeLists.txt | 4 + src/SketchPlugin/SketchPlugin_Offset.cpp | 759 ++++++++++++++++++ src/SketchPlugin/SketchPlugin_Offset.h | 145 ++++ src/SketchPlugin/SketchPlugin_Plugin.cpp | 5 +- src/SketchPlugin/SketchPlugin_Tools.cpp | 151 +++- src/SketchPlugin/SketchPlugin_Tools.h | 3 +- src/SketchPlugin/SketchPlugin_Validators.cpp | 28 +- src/SketchPlugin/Test/TestOffset1.py | 222 +++++ src/SketchPlugin/Test/TestOffset2.py | 61 ++ src/SketchPlugin/doc/SketchPlugin.rst | 1 + src/SketchPlugin/doc/TUI_offset.rst | 11 + src/SketchPlugin/doc/examples/offset.py | 22 + src/SketchPlugin/doc/images/Offset_panel.png | Bin 0 -> 11253 bytes src/SketchPlugin/doc/images/Offset_res.png | Bin 0 -> 8734 bytes src/SketchPlugin/doc/images/offset.png | Bin 0 -> 514 bytes src/SketchPlugin/doc/offsetFeature.rst | 59 ++ src/SketchPlugin/icons/offset.png | Bin 0 -> 514 bytes src/SketchPlugin/plugin-Sketch.xml | 32 + src/SketchSolver/CMakeLists.txt | 2 + .../PlaneGCSSolver/PlaneGCSSolver_Tools.cpp | 4 + .../SketchSolver_ConstraintOffset.cpp | 40 + .../SketchSolver_ConstraintOffset.h | 48 ++ src/SketcherPrs/CMakeLists.txt | 3 + src/SketcherPrs/SketcherPrs_Factory.cpp | 2 + src/SketcherPrs/SketcherPrs_Factory.h | 6 + src/SketcherPrs/SketcherPrs_Offset.cpp | 118 +++ src/SketcherPrs/SketcherPrs_Offset.h | 61 ++ src/SketcherPrs/SketcherPrs_Tools.cpp | 5 + src/SketcherPrs/icons/offset.png | Bin 0 -> 264 bytes 45 files changed, 2177 insertions(+), 62 deletions(-) create mode 100644 src/SketchAPI/SketchAPI_Offset.cpp create mode 100644 src/SketchAPI/SketchAPI_Offset.h create mode 100644 src/SketchPlugin/SketchPlugin_Offset.cpp create mode 100644 src/SketchPlugin/SketchPlugin_Offset.h create mode 100644 src/SketchPlugin/Test/TestOffset1.py create mode 100644 src/SketchPlugin/Test/TestOffset2.py create mode 100644 src/SketchPlugin/doc/TUI_offset.rst create mode 100644 src/SketchPlugin/doc/examples/offset.py create mode 100644 src/SketchPlugin/doc/images/Offset_panel.png create mode 100644 src/SketchPlugin/doc/images/Offset_res.png create mode 100644 src/SketchPlugin/doc/images/offset.png create mode 100644 src/SketchPlugin/doc/offsetFeature.rst create mode 100644 src/SketchPlugin/icons/offset.png create mode 100644 src/SketchSolver/SketchSolver_ConstraintOffset.cpp create mode 100644 src/SketchSolver/SketchSolver_ConstraintOffset.h create mode 100644 src/SketcherPrs/SketcherPrs_Offset.cpp create mode 100644 src/SketcherPrs/SketcherPrs_Offset.h create mode 100644 src/SketcherPrs/icons/offset.png diff --git a/src/GeomAPI/GeomAPI_BSpline.cpp b/src/GeomAPI/GeomAPI_BSpline.cpp index cb2aef956..b785aa77b 100644 --- a/src/GeomAPI/GeomAPI_BSpline.cpp +++ b/src/GeomAPI/GeomAPI_BSpline.cpp @@ -22,9 +22,11 @@ #include +#include + #define MY_BSPLINE (*(implPtr())) -GeomAPI_BSpline::GeomAPI_BSpline(const GeomCurvePtr& theCurve) +GeomAPI_BSpline::GeomAPI_BSpline (const GeomCurvePtr& theCurve) { GeomCurvePtr anUntrimmedCurve = theCurve->basisCurve(); Handle(Geom_Curve) aCurve = anUntrimmedCurve->impl(); @@ -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_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; +} diff --git a/src/GeomAPI/GeomAPI_BSpline.h b/src/GeomAPI/GeomAPI_BSpline.h index 95468bec4..251122dd8 100644 --- a/src/GeomAPI/GeomAPI_BSpline.h +++ b/src/GeomAPI/GeomAPI_BSpline.h @@ -27,6 +27,10 @@ #include class GeomAPI_Pnt; +class GeomAPI_BSpline; + +//! Pointer on the object +typedef std::shared_ptr 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 GeomBSplinePtr; + /// Convert any curve into a B-spline curve + GEOMAPI_EXPORT static GeomBSplinePtr convertToBSpline (const GeomCurvePtr& theCurve); +}; #endif diff --git a/src/GeomAPI/GeomAPI_Edge.cpp b/src/GeomAPI/GeomAPI_Edge.cpp index 41cfa84a8..817c68345 100644 --- a/src/GeomAPI/GeomAPI_Edge.cpp +++ b/src/GeomAPI/GeomAPI_Edge.cpp @@ -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_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(); diff --git a/src/GeomAlgoAPI/GeomAlgoAPI.i b/src/GeomAlgoAPI/GeomAlgoAPI.i index 04a925704..774f35fd9 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI.i +++ b/src/GeomAlgoAPI/GeomAlgoAPI.i @@ -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" diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp index 212bef8bd..dccf9bd13 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.cpp @@ -19,8 +19,16 @@ #include "GeomAlgoAPI_Offset.h" +#include + #include +#include + +#include +#include +#include +#include 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(); + 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(), 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); + } +} diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h index 56663fb5c..46623465e 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Offset.h @@ -23,6 +23,8 @@ #include #include +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& 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); diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.cpp index bfc4cb149..2a6c42497 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.cpp @@ -24,12 +24,15 @@ #include #include +#include #include #include +#include #include #include #include #include +#include #include #include @@ -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(); 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(); } //================================================================================================= diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.h b/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.h index 0ae048a2a..36b2d10fc 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_WireBuilder.h @@ -21,15 +21,25 @@ #define GeomAlgoAPI_WireBuilder_H_ #include "GeomAlgoAPI.h" +#include "GeomAlgoAPI_MakeShapeCustom.h" #include /// \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. diff --git a/src/Model/Model_AttributeIntArray.cpp b/src/Model/Model_AttributeIntArray.cpp index 9db430b22..d6602bba7 100644 --- a/src/Model/Model_AttributeIntArray.cpp +++ b/src/Model/Model_AttributeIntArray.cpp @@ -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) diff --git a/src/SketchAPI/CMakeLists.txt b/src/SketchAPI/CMakeLists.txt index 617498e0f..7934de9f2 100644 --- a/src/SketchAPI/CMakeLists.txt +++ b/src/SketchAPI/CMakeLists.txt @@ -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 diff --git a/src/SketchAPI/SketchAPI.i b/src/SketchAPI/SketchAPI.i index d46066e6c..98bc2c6ab 100644 --- a/src/SketchAPI/SketchAPI.i +++ b/src/SketchAPI/SketchAPI.i @@ -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) @@ -579,6 +580,7 @@ %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 index 000000000..392d07fe0 --- /dev/null +++ b/src/SketchAPI/SketchAPI_Offset.cpp @@ -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 +//-------------------------------------------------------------------------------------- +#include +#include +#include + +//-------------------------------------------------------------------------------------- +SketchAPI_Offset::SketchAPI_Offset (const std::shared_ptr & theFeature) + : ModelHighAPI_Interface(theFeature) +{ + initialize(); +} + +SketchAPI_Offset::SketchAPI_Offset (const std::shared_ptr & theFeature, + const std::list > & 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 > SketchAPI_Offset::offset() const +{ + std::list aList = feature()->reflist(SketchPlugin_Constraint::ENTITY_B())->list(); + std::list anIntermediate; + std::list::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 > aList = offset(); + std::list >::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 << ")" < + +#include + +#include +#include +//-------------------------------------------------------------------------------------- +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 & theFeature); + /// Constructor with values + SKETCHAPI_EXPORT + SketchAPI_Offset(const std::shared_ptr & theFeature, + const std::list > & 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 > offset() const; + + /// Dump wrapped feature + virtual void dump(ModelHighAPI_Dumper& theDumper) const; +}; + +//! Pointer on Offset object +typedef std::shared_ptr OffsetPtr; + +//-------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------- +#endif /* SRC_SKETCHAPI_SKETCHAPI_OFFSET_H_ */ diff --git a/src/SketchAPI/SketchAPI_Sketch.cpp b/src/SketchAPI/SketchAPI_Sketch.cpp index a4a9bec25..2f6ffcdfa 100644 --- a/src/SketchAPI/SketchAPI_Sketch.cpp +++ b/src/SketchAPI/SketchAPI_Sketch.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include //-------------------------------------------------------------------------------------- #include @@ -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_Sketch::addMirror( return MirrorPtr(new SketchAPI_Mirror(aFeature, theMirrorLine, theObjects)); } +//-------------------------------------------------------------------------------------- +std::shared_ptr SketchAPI_Sketch::addOffset( + const std::list > & theObjects, + const ModelHighAPI_Double & theValue, + const bool theReversed) +{ + std::shared_ptr aFeature = + compositeFeature()->addFeature(SketchPlugin_Offset::ID()); + return OffsetPtr(new SketchAPI_Offset(aFeature, theObjects, theValue, theReversed)); +} + //-------------------------------------------------------------------------------------- std::shared_ptr SketchAPI_Sketch::addTranslation( const std::list > & theObjects, diff --git a/src/SketchAPI/SketchAPI_Sketch.h b/src/SketchAPI/SketchAPI_Sketch.h index 57d424760..3911015de 100644 --- a/src/SketchAPI/SketchAPI_Sketch.h +++ b/src/SketchAPI/SketchAPI_Sketch.h @@ -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 > & theObjects); + /// Add offset + SKETCHAPI_EXPORT + std::shared_ptr addOffset( + const std::list > & theObjects, + const ModelHighAPI_Double & theValue, + const bool theReversed); + /// Add translation SKETCHAPI_EXPORT std::shared_ptr addTranslation( diff --git a/src/SketchAPI/SketchAPI_swig.h b/src/SketchAPI/SketchAPI_swig.h index 43422dd3c..e43232dd3 100644 --- a/src/SketchAPI/SketchAPI_swig.h +++ b/src/SketchAPI/SketchAPI_swig.h @@ -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" diff --git a/src/SketchPlugin/CMakeLists.txt b/src/SketchPlugin/CMakeLists.txt index 8c33ed0ed..aa51871dc 100644 --- a/src/SketchPlugin/CMakeLists.txt +++ b/src/SketchPlugin/CMakeLists.txt @@ -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 index 000000000..f3f8d2b89 --- /dev/null +++ b/src/SketchPlugin/SketchPlugin_Offset.cpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 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 anEdgesList = aSelectedEdges->list(); + + // 4. Put all selected edges in a set to pass them into findWireOneWay() below + std::set anEdgesSet; + std::list::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 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 aStartPoint, anEndPoint; + SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint); + + // 5.b. Find a chain of edges + std::list 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::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 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 anOffsetShape( + new GeomAlgoAPI_Offset(aPlane, aWireShape, aValue*aSign)); + + std::shared_ptr 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& theEndPoint, + std::set& theEdgesSet, + std::set& theProcessedEdgesSet, + std::list& 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 aCoincPoints = + SketchPlugin_Tools::findPointsCoincidentToPoint(theEndPoint); + + std::set::iterator aPointsIt = aCoincPoints.begin(); + for (; aPointsIt != aCoincPoints.end(); aPointsIt++) { + AttributePoint2DPtr aP = (*aPointsIt); + FeaturePtr aCoincFeature = std::dynamic_pointer_cast(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 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 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 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 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 > aMapExistent; + std::list anObjectsToRemove; + std::list aSelectedList = aSelectedRefList->list(); + for (std::list::iterator aSIt = aSelectedList.begin(); + aSIt != aSelectedList.end(); ++aSIt) { + aMapExistent[*aSIt] = std::list(); + } + 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 >::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 anOffsetBaseBackRefs; + std::set aProcessedOffsets; + for (std::list::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& anImages = aMapExistent[*aSIt]; + std::list::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::iterator anIt = anOffsetBaseBackRefs.begin(); + anIt != anOffsetBaseBackRefs.end(); ++anIt) { + anOffsetToBaseMap->setValue(anIndex++, *anIt, false); + } + + // remove unused objects + std::set aSet; + for (std::list::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& 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::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& thePoolOfFeatures) +{ + if (theShape->shapeType() != GeomAPI_Shape::EDGE) + return; + + std::shared_ptr aResEdge(new GeomAPI_Edge(theShape)); + + std::shared_ptr aFP, aLP; + std::shared_ptr aFP3d = aResEdge->firstPoint(); + std::shared_ptr 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 + (theFeature->attribute(SketchPlugin_Line::START_ID()))->setValue(aFP); + std::dynamic_pointer_cast + (theFeature->attribute(SketchPlugin_Line::END_ID()))->setValue(aLP); + } + else if (aResEdge->isArc()) { + std::shared_ptr aCircEdge = aResEdge->circle(); + std::shared_ptr aCP3d = aCircEdge->center(); + std::shared_ptr 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 + (theFeature->attribute(SketchPlugin_Arc::END_ID()))->setValue(aLP); + std::dynamic_pointer_cast + (theFeature->attribute(SketchPlugin_Arc::START_ID()))->setValue(aFP); + std::dynamic_pointer_cast + (theFeature->attribute(SketchPlugin_Arc::CENTER_ID()))->setValue(aCP); + theFeature->data()->blockSendAttributeUpdated(aWasBlocked); + } + else if (aResEdge->isCircle()) { + std::shared_ptr aCircEdge = aResEdge->circle(); + std::shared_ptr aCP3d = aCircEdge->center(); + std::shared_ptr aCP = sketch()->to2D(aCP3d); + + findOrCreateFeatureByKind(sketch(), SketchPlugin_Circle::ID(), theFeature, thePoolOfFeatures); + + std::dynamic_pointer_cast + (theFeature->attribute(SketchPlugin_Circle::CENTER_ID()))->setValue(aCP); + theFeature->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aCircEdge->radius()); + } + else if (aResEdge->isEllipse()) { + std::shared_ptr 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 + (theFeature->attribute(SketchPlugin_EllipticArc::CENTER_ID()))->setValue(aCP); + std::dynamic_pointer_cast + (theFeature->attribute(SketchPlugin_EllipticArc::FIRST_FOCUS_ID()))->setValue(aFocus); + std::dynamic_pointer_cast + (theFeature->attribute(SketchPlugin_EllipticArc::START_POINT_ID()))->setValue(aFP); + std::dynamic_pointer_cast + (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 + (theFeature->attribute(SketchPlugin_Ellipse::CENTER_ID()))->setValue(aCP); + std::dynamic_pointer_cast + (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& aResults = theFeature->results(); + for (std::list::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& 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 + (theResult->attribute(SketchPlugin_BSpline::POLES_ID())); + std::list aPoles = aBSpline->poles(); + aPolesAttr->setSize((int)aPoles.size()); + std::list::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 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::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 aKnots = aBSpline->knots(); + int aSize = (int)aKnots.size(); + aKnotsAttr->setSize(aSize); + std::list::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 aMultiplicities = aBSpline->mults(); + aSize = (int)aMultiplicities.size(); + aMultsAttr->setSize(aSize); + std::list::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 anOffsetList = anOffsetAttr->list(); + std::set aFeaturesToBeRemoved; + for (std::list::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 anEdgesList = aSelectedEdges->list(); + + // Empty set + std::set anEdgesSet; + + // Processed set + std::set aProcessedEdgesSet; + + // Put all selected edges in a set to avoid adding them in reflist(EDGES_ID()) + std::set aSelectedSet; + std::list::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 aStartPoint, anEndPoint; + SketchPlugin_SegmentationTools::getFeaturePoints(aFeature, aStartPoint, anEndPoint); + + std::list 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::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 index 000000000..3a809f302 --- /dev/null +++ b/src/SketchPlugin/SketchPlugin_Offset.h @@ -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 +#include + +#include + +#include + +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 >& theOffsetAlgos); + + /// Create BSpline or BSplinePeriodic sketch feature from theEdge + void mkBSpline (FeaturePtr& theResult, const GeomEdgePtr& theEdge, + std::list& 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& 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& theEndPoint, + std::set& theEdgesSet, + std::set& theProcessedEdgesSet, + std::list& theChain, + const bool isPrepend = false); +}; + +#endif diff --git a/src/SketchPlugin/SketchPlugin_Plugin.cpp b/src/SketchPlugin/SketchPlugin_Plugin.cpp index a2f0de04a..b71ff3c37 100644 --- a/src/SketchPlugin/SketchPlugin_Plugin.cpp +++ b/src/SketchPlugin/SketchPlugin_Plugin.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -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 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); } diff --git a/src/SketchPlugin/SketchPlugin_Tools.cpp b/src/SketchPlugin/SketchPlugin_Tools.cpp index bbdefcbdd..ed4084afe 100644 --- a/src/SketchPlugin/SketchPlugin_Tools.cpp +++ b/src/SketchPlugin/SketchPlugin_Tools.cpp @@ -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 #include +#include #include #include @@ -113,10 +115,10 @@ std::shared_ptr getCoincidencePoint(const FeaturePtr theStartCoin return aPnt; } -std::set findCoincidentConstraints(const FeaturePtr& theFeature) +std::set findCoincidentConstraints(const ObjectPtr& theObject) { std::set aCoincident; - const std::set& aRefsList = theFeature->data()->refsToMe(); + const std::set& aRefsList = theObject->data()->refsToMe(); std::set::const_iterator aIt; for (aIt = aRefsList.cbegin(); aIt != aRefsList.cend(); ++aIt) { FeaturePtr aConstrFeature = std::dynamic_pointer_cast((*aIt)->owner()); @@ -199,26 +201,29 @@ std::set 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 >::iterator aFound1 = find(thePoint1); - std::list< std::set >::iterator aFound2 = find(thePoint2); + auto aFound1 = find(thePoint1, theIndex1); + auto aFound2 = find(thePoint2, theIndex2); if (aFound1 == myCoincidentPoints.end()) { if (aFound2 == myCoincidentPoints.end()) { - std::set aNewSet; - aNewSet.insert(thePoint1); + std::map > 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 >::iterator aFound = find(thePoint); - if (aFound == myCoincidentPoints.end()) - return std::set(); - return *aFound; + std::set 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(it->first); + if (aPoint) + aCoincPoints.insert(aPoint); + else { + AttributePoint2DArrayPtr aPointArray = + std::dynamic_pointer_cast(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( + 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( + 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 aCoincidences = SketchPlugin_Tools::findCoincidentConstraints(theFeature); - std::set::const_iterator aCIt = aCoincidences.begin(); + if (theFeature->getKind() == SketchPlugin_Point::ID()) { + std::set aCoincToRes = + SketchPlugin_Tools::findCoincidentConstraints(theFeature->lastResult()); + aCoincidences.insert(aCoincToRes.begin(), aCoincToRes.end()); + } std::set::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 aCoincidences; @@ -270,30 +311,69 @@ private: std::set::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(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(aRefAttr->attr()); + AttributePoint2DArrayPtr aPointArray = + std::dynamic_pointer_cast(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 >::iterator find(const AttributePoint2DPtr& thePoint) + std::list< std::map > >::iterator find(const AttributePtr& thePoint, + const int theIndex) { - std::list< std::set >::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(aPointsArray)->size() - 1; + } + if (aPointsArray) + return find(aPointsArray, anIndex); + } return myCoincidentPoints.end(); } private: - std::list< std::set > myCoincidentPoints; + std::list< std::map > > myCoincidentPoints; }; std::set findPointsCoincidentToPoint(const AttributePoint2DPtr& thePoint) @@ -302,6 +382,7 @@ std::set 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( theFeature->attribute(aStartAttributeName)); diff --git a/src/SketchPlugin/SketchPlugin_Tools.h b/src/SketchPlugin/SketchPlugin_Tools.h index eb9949e7d..eb86f8b33 100644 --- a/src/SketchPlugin/SketchPlugin_Tools.h +++ b/src/SketchPlugin/SketchPlugin_Tools.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,7 @@ void clearExpressions(FeaturePtr theFeature); std::shared_ptr getCoincidencePoint(const FeaturePtr theStartCoin); /// Find all Coincident constraints referred to the feature or its attribute -std::set findCoincidentConstraints(const FeaturePtr& theFeature); +std::set findCoincidentConstraints(const ObjectPtr& theObject); /// Finds lines coincident at point /// \param[in] theStartCoin coincidence feature diff --git a/src/SketchPlugin/SketchPlugin_Validators.cpp b/src/SketchPlugin/SketchPlugin_Validators.cpp index 4a6cc4b25..feaec5c66 100644 --- a/src/SketchPlugin/SketchPlugin_Validators.cpp +++ b/src/SketchPlugin/SketchPlugin_Validators.cpp @@ -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(theAttribute->owner()); AttributeRefListPtr aSelAttr = std::dynamic_pointer_cast(theAttribute); + std::set aSelected; AttributeRefListPtr aRefListOfInitial = std::dynamic_pointer_cast( aFeature->attribute(SketchPlugin_Constraint::ENTITY_A())); @@ -524,6 +526,12 @@ bool SketchPlugin_CopyValidator::isValid(const AttributePtr& theAttribute, std::list::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(*anObjIter); + if (aFeature) { + const std::list& aResults = aFeature->results(); + for (std::list::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 index 000000000..24b162247 --- /dev/null +++ b/src/SketchPlugin/Test/TestOffset1.py @@ -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 index 000000000..e15e35d7c --- /dev/null +++ b/src/SketchPlugin/Test/TestOffset2.py @@ -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()) diff --git a/src/SketchPlugin/doc/SketchPlugin.rst b/src/SketchPlugin/doc/SketchPlugin.rst index af4cefe57..00eb370da 100644 --- a/src/SketchPlugin/doc/SketchPlugin.rst +++ b/src/SketchPlugin/doc/SketchPlugin.rst @@ -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 index 000000000..6175182e1 --- /dev/null +++ b/src/SketchPlugin/doc/TUI_offset.rst @@ -0,0 +1,11 @@ + + .. _tui_create_offset: + +Create Offset +============= + +.. literalinclude:: examples/offset.py + :linenos: + :language: python + +:download:`Download this script ` diff --git a/src/SketchPlugin/doc/examples/offset.py b/src/SketchPlugin/doc/examples/offset.py new file mode 100644 index 000000000..881db9a40 --- /dev/null +++ b/src/SketchPlugin/doc/examples/offset.py @@ -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 index 0000000000000000000000000000000000000000..d34ad6c3c35e1f7692de854f1b8ceb0c8bffabee GIT binary patch literal 11253 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

Nj7oe|hQhu1>Lrs~sslq>y=GL2+TlZ-(H;2 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 Eg88)IM*si- literal 0 HcmV?d00001 diff --git a/src/SketchPlugin/doc/offsetFeature.rst b/src/SketchPlugin/doc/offsetFeature.rst new file mode 100644 index 000000000..ce42254df --- /dev/null +++ b/src/SketchPlugin/doc/offsetFeature.rst @@ -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 index 0000000000000000000000000000000000000000..8ce6065d0476078a21129d3fb64857487202937a GIT binary patch literal 514 zcmV+d0{#7oP)95nv(GCP|uPEOs36swp>$<3Xu<~@i+9&a&?_m~OV zgWgn{tJQ?3h{(m+Z!a3JbGU=kJt2KJF${dJkE}>K29-oqWc^|n#mU{^BYh?k&P^I=hgc8 zK?3aqo$Z6aa)jff7HKHHZ*K1P!m0eATl%j0NL;}&)Pq0S+_(;o;uhW|E@l(?QB{d$ zd-viL9wm;H$8iuW_eQ(&PtQkO&HkB@4l?+HRRenP7r`iN8~Vm3-T(jq07*qoM6N<$ Eg88)IM*si- literal 0 HcmV?d00001 diff --git a/src/SketchPlugin/plugin-Sketch.xml b/src/SketchPlugin/plugin-Sketch.xml index 76800d383..ef62acfca 100644 --- a/src/SketchPlugin/plugin-Sketch.xml +++ b/src/SketchPlugin/plugin-Sketch.xml @@ -18,6 +18,7 @@ SketchConstraintCoincidence SketchConstraintCoincidenceInternal SketchConstraintMirror SketchConstraintAngle SketchMultiRotation SketchMultiTranslation + SketchOffset SketchConstraintCollinear SketchConstraintMiddle" when_nested="accept abort" title="Sketch" @@ -994,6 +995,37 @@ + + + + + + + + + + + + diff --git a/src/SketchSolver/CMakeLists.txt b/src/SketchSolver/CMakeLists.txt index 5dba33dca..5df25991e 100644 --- a/src/SketchSolver/CMakeLists.txt +++ b/src/SketchSolver/CMakeLists.txt @@ -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 diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp index cbcb03bc8..fb7185c46 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,7 @@ #include #include #include +#include #include #include @@ -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 index 000000000..dbe419741 --- /dev/null +++ b/src/SketchSolver/SketchSolver_ConstraintOffset.cpp @@ -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 + + +void SketchSolver_ConstraintOffset::getAttributes( + EntityWrapperPtr&, + std::vector&) +{ +} + +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 index 000000000..45d760b08 --- /dev/null +++ b/src/SketchSolver/SketchSolver_ConstraintOffset.h @@ -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 + +/** \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&); +}; + +#endif diff --git a/src/SketcherPrs/CMakeLists.txt b/src/SketcherPrs/CMakeLists.txt index a8d3dc4f5..8a123c733 100644 --- a/src/SketcherPrs/CMakeLists.txt +++ b/src/SketcherPrs/CMakeLists.txt @@ -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) diff --git a/src/SketcherPrs/SketcherPrs_Factory.cpp b/src/SketcherPrs/SketcherPrs_Factory.cpp index 1c4187aaf..59d73c0f8 100644 --- a/src/SketcherPrs/SketcherPrs_Factory.cpp +++ b/src/SketcherPrs/SketcherPrs_Factory.cpp @@ -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, diff --git a/src/SketcherPrs/SketcherPrs_Factory.h b/src/SketcherPrs/SketcherPrs_Factory.h index 9caf0a1a5..286ce0158 100644 --- a/src/SketcherPrs/SketcherPrs_Factory.h +++ b/src/SketcherPrs/SketcherPrs_Factory.h @@ -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 index 000000000..a9d3ffbd3 --- /dev/null +++ b/src/SketcherPrs/SketcherPrs_Offset.cpp @@ -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 + +#include +#include + +#include +#include + + +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&/* 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 index 000000000..04e7d76de --- /dev/null +++ b/src/SketcherPrs/SketcherPrs_Offset.h @@ -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& 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 diff --git a/src/SketcherPrs/SketcherPrs_Tools.cpp b/src/SketcherPrs/SketcherPrs_Tools.cpp index d535a6d7f..cdf0a2391 100644 --- a/src/SketcherPrs/SketcherPrs_Tools.cpp +++ b/src/SketcherPrs/SketcherPrs_Tools.cpp @@ -95,6 +95,11 @@ ObjectPtr getResult(ModelAPI_Feature* theFeature, const std::string& theAttrName std::shared_ptr getShape(ObjectPtr theObject) { ResultConstructionPtr aRes = std::dynamic_pointer_cast(theObject); + if (!aRes.get()) { + FeaturePtr aFeature = std::dynamic_pointer_cast(theObject); + if (aFeature.get()) + aRes = std::dynamic_pointer_cast(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 index 0000000000000000000000000000000000000000..0e0f73786e0b43827851da073914e54ee6f52ba0 GIT binary patch literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=Dh+L6~vJ#O${~!MUC;jv*SsQzt}o9aa!I8)JuN2QaW-DFvI&wu?ybLq>!>Px4w z`FXW#sfw?X&Dg!cCTwfTpZ*B9|JM#xG8eoPXyA#cVAhPz)>RN^t_C`k!PC{xWt~$( F69DflSq=aI literal 0 HcmV?d00001 -- 2.39.2