From: Artem Zhidkov Date: Mon, 25 May 2020 12:05:31 +0000 (+0300) Subject: Task #3231: Sketcher Offset of a curve X-Git-Tag: V9_6_0a1~64 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=747178d21dce4fb034ff0e84c1280406ab64d566;p=modules%2Fshaper.git 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. --- 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 000000000..d34ad6c3c Binary files /dev/null and b/src/SketchPlugin/doc/images/Offset_panel.png differ diff --git a/src/SketchPlugin/doc/images/Offset_res.png b/src/SketchPlugin/doc/images/Offset_res.png new file mode 100644 index 000000000..701faf072 Binary files /dev/null and b/src/SketchPlugin/doc/images/Offset_res.png differ diff --git a/src/SketchPlugin/doc/images/offset.png b/src/SketchPlugin/doc/images/offset.png new file mode 100644 index 000000000..8ce6065d0 Binary files /dev/null and b/src/SketchPlugin/doc/images/offset.png differ diff --git a/src/SketchPlugin/doc/offsetFeature.rst b/src/SketchPlugin/doc/offsetFeature.rst new file mode 100644 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 000000000..8ce6065d0 Binary files /dev/null and b/src/SketchPlugin/icons/offset.png differ 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 000000000..0e0f73786 Binary files /dev/null and b/src/SketcherPrs/icons/offset.png differ