From 11e506a02bbd919d5977b3a25cd4cd84ba54fda4 Mon Sep 17 00:00:00 2001 From: Artem Zhidkov Date: Fri, 22 May 2020 19:55:22 +0300 Subject: [PATCH] Task #3230: Sketcher: create a curve passing through selected points or vertices of a polyline * Implement the Curve Fitting feature. * Implement the de Boor scheme for periodic and non-periodic B-spline evaluation. * Implement interpolation and approximation modes of the feature. * Add creation of the control polygon. * Reordering points on the corresponding button click * Python API for this feature * User documentation --- src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.cpp | 175 +++++--- src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.h | 18 +- src/ModelAPI/ModelAPI_Feature.h | 5 + .../ModuleBase_WidgetOptionalBox.cpp | 6 +- src/ModuleBase/ModuleBase_WidgetOptionalBox.h | 1 + src/SketchAPI/SketchAPI.i | 2 + src/SketchAPI/SketchAPI_Sketch.cpp | 63 +++ src/SketchAPI/SketchAPI_Sketch.h | 17 +- src/SketchPlugin/CMakeLists.txt | 2 + .../SketchPlugin_CurveFitting.cpp | 386 ++++++++++++++++++ src/SketchPlugin/SketchPlugin_CurveFitting.h | 154 +++++++ .../SketchPlugin_MacroBSpline.cpp | 5 +- src/SketchPlugin/SketchPlugin_MacroBSpline.h | 12 +- src/SketchPlugin/SketchPlugin_Plugin.cpp | 6 + src/SketchPlugin/SketchPlugin_Validators.cpp | 21 + src/SketchPlugin/SketchPlugin_Validators.h | 16 + src/SketchPlugin/SketchPlugin_msg_en.ts | 126 ++++++ src/SketchPlugin/SketchPlugin_msg_fr.ts | 120 ++++++ src/SketchPlugin/doc/TUI_curvefitting.rst | 23 ++ src/SketchPlugin/doc/curveFittingFeature.rst | 89 ++++ .../doc/examples/approximation.py | 38 ++ .../doc/examples/interpolation.py | 47 +++ src/SketchPlugin/doc/images/curvefitting.png | Bin 0 -> 464 bytes .../images/curvefitting_approximation_res.png | Bin 0 -> 3381 bytes .../images/curvefitting_interpolation_res.png | Bin 0 -> 3374 bytes .../curvefitting_panel_approximation.png | Bin 0 -> 9502 bytes .../curvefitting_panel_interpolation.png | Bin 0 -> 8615 bytes src/SketchPlugin/icons/curvefitting.png | Bin 0 -> 464 bytes src/SketchPlugin/plugin-Sketch.xml | 55 +++ .../PlaneGCSSolver_GeoExtensions.cpp | 200 +++++---- .../PlaneGCSSolver_GeoExtensions.h | 39 +- 31 files changed, 1468 insertions(+), 158 deletions(-) create mode 100644 src/SketchPlugin/SketchPlugin_CurveFitting.cpp create mode 100644 src/SketchPlugin/SketchPlugin_CurveFitting.h create mode 100644 src/SketchPlugin/doc/TUI_curvefitting.rst create mode 100644 src/SketchPlugin/doc/curveFittingFeature.rst create mode 100644 src/SketchPlugin/doc/examples/approximation.py create mode 100644 src/SketchPlugin/doc/examples/interpolation.py create mode 100644 src/SketchPlugin/doc/images/curvefitting.png create mode 100644 src/SketchPlugin/doc/images/curvefitting_approximation_res.png create mode 100644 src/SketchPlugin/doc/images/curvefitting_interpolation_res.png create mode 100644 src/SketchPlugin/doc/images/curvefitting_panel_approximation.png create mode 100644 src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png create mode 100644 src/SketchPlugin/icons/curvefitting.png diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.cpp b/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.cpp index ffec79e30..2ec8797c6 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.cpp +++ b/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.cpp @@ -24,46 +24,51 @@ #include #include +#include #include -#include -#include -#include -#include +#include +#include +#include #include +#include #include #include +#include +#include +#include +#include +#include -static void reorder(Handle(TColgp_HArray1OfPnt)& thePoints); - -//================================================================================================= GeomEdgePtr GeomAlgoAPI_CurveBuilder::edge(const std::list& thePoints, - const bool theIsClosed, - const bool theIsToReorder, - const GeomDirPtr& theStartTangent, - const GeomDirPtr& theEndTangent) + const bool thePeriodic, + const bool theIsToReorder, + const GeomDirPtr& theStartTangent, + const GeomDirPtr& theEndTangent) { - // Prepare points array - Handle(TColgp_HArray1OfPnt) aPoints = new TColgp_HArray1OfPnt(1, (int)thePoints.size()); - std::list::const_iterator anIt = thePoints.begin(); - for (int i = 1; anIt != thePoints.end(); anIt++, i++) { - GeomPointPtr aPoint = *anIt; - aPoints->SetValue(i, aPoint->impl()); - } + std::list aPointsCopy = thePoints; // If the curve to be closed - remove last point if it is too close to the first one - bool isClose = aPoints->First().Distance(aPoints->Last()) <= gp::Resolution(); - if (isClose && theIsClosed) { - aPoints->Resize(aPoints->Lower(), aPoints->Upper() - 1, Standard_True); + bool isClose = aPointsCopy.front()->distance(aPointsCopy.back()) <= gp::Resolution(); + if (isClose && thePeriodic) { + aPointsCopy.pop_back(); } // Reorder points if required if (theIsToReorder) { - reorder(aPoints); + reorderPoints(aPointsCopy); + } + + // Prepare points array + Handle(TColgp_HArray1OfPnt) aPoints = new TColgp_HArray1OfPnt(1, (int)aPointsCopy.size()); + std::list::const_iterator anIt = aPointsCopy.begin(); + for (int i = 1; anIt != aPointsCopy.end(); anIt++, i++) { + GeomPointPtr aPoint = *anIt; + aPoints->SetValue(i, aPoint->impl()); } // Initialize interpolator - GeomAPI_Interpolate anInterp(aPoints, theIsClosed, gp::Resolution()); + GeomAPI_Interpolate anInterp(aPoints, thePeriodic, gp::Resolution()); // Assign tangents if defined if (theStartTangent && theEndTangent) { @@ -92,54 +97,114 @@ GeomEdgePtr GeomAlgoAPI_CurveBuilder::edge(const std::list& thePoi return aResultShape; } -//================ Auxiliary functions ======================================================== -void reorder(Handle(TColgp_HArray1OfPnt)& thePoints) +GeomEdgePtr GeomAlgoAPI_CurveBuilder::approximate(const std::list& thePoints, + const bool thePeriodic, + const double thePrecision) +{ + // Prepare points array to be able to build a surface. + // This surface is based on two sets of points: the first is an original and + // the second is shifted along orthogonal direction. + // This is a workaround, because GeomAPI_PointsToBSpline algorithm cannot produce + // the periodic curve, but GeomAPI_PointsToBSplineSurface can. + TColgp_Array2OfPnt aPoints(1, (int)thePoints.size(), 1, 2); + gp_Pnt aPlaneBase[3]; // base points to calculate the normal direction + int aNbPlanePoints = 0; + gp_Dir aNormal; + std::list::const_iterator anIt = thePoints.begin(); + for (int i = 1; anIt != thePoints.end(); anIt++, i++) { + const gp_Pnt& aPoint = (*anIt)->impl(); + aPoints.SetValue(i, 1, aPoint); + aPoints.SetValue(i, 2, aPoint); + if (aNbPlanePoints < 3) { + if (aNbPlanePoints == 0 || + aPoint.SquareDistance(aPlaneBase[0]) > Precision::SquareConfusion()) + aPlaneBase[aNbPlanePoints++] = aPoint; + if (aNbPlanePoints == 3) { + gp_Vec aVec12(aPlaneBase[0], aPlaneBase[1]); + gp_Vec aVec13(aPlaneBase[0], aPlaneBase[2]); + if (aVec12.CrossSquareMagnitude(aVec13) > Precision::SquareConfusion()) + aNormal = gp_Dir(aVec12 ^ aVec13); + else + --aNbPlanePoints; + } + } + } + if (aNbPlanePoints < 3) + aNormal = gp::DZ(); + // shifted points + for (int i = aPoints.LowerRow(); i <= aPoints.UpperRow(); i++) + aPoints.ChangeValue(i, 2).ChangeCoord() += aNormal.XYZ(); + + // If the curve to be closed - remove last point if it is too close to the first one + bool isClose = aPoints.Value(aPoints.LowerRow(), 1).Distance( + aPoints.Value(aPoints.UpperRow(), 1)) <= gp::Resolution(); + if (isClose && thePeriodic) { + aPoints.Resize(aPoints.LowerRow(), aPoints.UpperRow() - 1, + aPoints.LowerCol(), aPoints.UpperCol(), Standard_True); + } + + // Initialize and perform approximator + static const Standard_Integer DEGREE_MIN = 3; + static const Standard_Integer DEGREE_MAX = 8; + GeomAPI_PointsToBSplineSurface anApprox; + anApprox.Init(aPoints, Approx_ChordLength, DEGREE_MIN, DEGREE_MAX, + GeomAbs_C2, thePrecision, thePeriodic); + + // Set result in form of edge + TopoDS_Edge anEdge; + if (anApprox.IsDone()) { + // build a curve along U-direction of the surface + Handle(Geom_BSplineSurface) aSurface = anApprox.Surface(); + Handle(Geom_Curve) aCurve = aSurface->VIso(aSurface->VKnots().First()); + + anEdge = BRepBuilderAPI_MakeEdge(aCurve).Edge(); + } + + GeomEdgePtr aResultShape(new GeomAPI_Edge); + aResultShape->setImpl(new TopoDS_Shape(anEdge)); + + return aResultShape; +} + +void GeomAlgoAPI_CurveBuilder::reorderPoints(std::list& thePoints) { - if (thePoints->Length() < 3) { + if (thePoints.size() < 3) { return; } - int aNbPoints = thePoints->Length(); int aNbDup = 0; - gp_Pnt aPrevPnt = thePoints->Value(1); - for (int i = 1; i < aNbPoints; i++) { - gp_Pnt aPnt = thePoints->Value(i); - int aNearest = 0; + std::list::iterator aPIt = thePoints.begin(); + GeomPointPtr aPrevPnt = *aPIt; + for (; aPIt != thePoints.end(); ++aPIt) { + GeomPointPtr aPnt = *aPIt; + std::list::iterator aNextIt = aPIt; + std::list::iterator aNearestIt = ++aNextIt; double aMinDist = RealLast(); - for (int j = i + 1; j <= aNbPoints; j++) { - double aDist = aPnt.SquareDistance(thePoints->Value(j)); + while (aNextIt != thePoints.end()) { + double aDist = aPnt->distance(*aNextIt); + if (aDist < Precision::Confusion()) { + // remove duplicates + std::list::iterator aRemoveIt = aNextIt++; + thePoints.erase(aRemoveIt); + continue; + } if (aDist < aMinDist && (aMinDist - aDist) > Precision::Confusion()) { - aNearest = j; + aNearestIt = aNextIt; aMinDist = aDist; } + ++aNextIt; } - if (aNearest > 0 && aNearest != i + 1) { + aNextIt = aPIt; ++aNextIt; + if (aNearestIt != aNextIt) { // Keep given order of points to use it in case of equidistant candidates // .-<---<-. // / \ // o o o c o->o->o->o->n o o // | | | // i i+1 nearest - gp_Pnt aNearestPnt = thePoints->Value(aNearest); - for (int j = aNearest; j > i + 1; j--) { - thePoints->SetValue(j, thePoints->Value(j - 1)); - } - thePoints->SetValue(i + 1, aNearestPnt); - } - if (aPrevPnt.Distance(thePoints->Value(i + 1)) <= Precision::Confusion()) - aNbDup++; - else - aPrevPnt = thePoints->Value(i + 1); - } - - if (aNbDup > 0) { - Handle(TColgp_HArray1OfPnt) aTmpPoints = new TColgp_HArray1OfPnt(1, aNbPoints - aNbDup); - for (int j = 1, i = 1; i <= aNbPoints; i++) { - if (i == 1 || aPrevPnt.Distance(thePoints->Value(i)) > Precision::Confusion()) { - aTmpPoints->SetValue(j++, thePoints->Value(i)); - aPrevPnt = thePoints->Value(i); - } + GeomPointPtr aPointToMove = *aNearestIt; + thePoints.erase(aNearestIt); + thePoints.insert(aNextIt, aPointToMove); } - thePoints = aTmpPoints; } } \ No newline at end of file diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.h b/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.h index e35073b8d..57eaf311b 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.h @@ -27,23 +27,35 @@ /// \class GeomAlgoAPI_CurveBuilder /// \ingroup DataAlgo -/// \brief Allows to create interpolation curve. +/// \brief Allows to create a curve by the list of point. class GeomAlgoAPI_CurveBuilder { public: /// \brief Creates an interpolation curve from points. /// \param[in] thePoints list of points. - /// \param[in] theIsClosed defines whether the curve to be closed. + /// \param[in] thePeriodic defines whether the curve to be periodic. /// \param[in] theIsToReorder defines whether to change the order of points to construct /// the shortest curve. /// \param[in] theStartTangent vector tangent to the start of curve. /// \param[in] theEndTangent vector tangent to the end of curve. /// \return Interpolation curve (edge). Empty in case of error or bad input. GEOMALGOAPI_EXPORT static GeomEdgePtr edge(const std::list& thePoints, - const bool theIsClosed, + const bool thePeriodic, const bool theIsToReorder, const GeomDirPtr& theStartTangent, const GeomDirPtr& theEndTangent); + + /// \brief Approximate the given points by a curve. + /// \param[in] thePoints list of points. + /// \param[in] thePeriodic defines whether the curve to be periodic. + /// \param[in] thePrecision how close the curve should be to the points. + /// \return Apporimation curve (edge). Empty in case of error or bad input. + GEOMALGOAPI_EXPORT static GeomEdgePtr approximate(const std::list& thePoints, + const bool thePeriodic, + const double thePrecision); + + /// \brief Reoder the list of points to get a polyline of minimal length + GEOMALGOAPI_EXPORT static void reorderPoints(std::list& thePoints); }; #endif diff --git a/src/ModelAPI/ModelAPI_Feature.h b/src/ModelAPI/ModelAPI_Feature.h index 8c2316086..77cd55b96 100644 --- a/src/ModelAPI/ModelAPI_Feature.h +++ b/src/ModelAPI/ModelAPI_Feature.h @@ -198,6 +198,11 @@ class ModelAPI_Feature : public ModelAPI_Object { return data()->refattr(theID); } + /// Returns the refattrlist attribute by the identifier + inline std::shared_ptr refattrlist(const std::string& theID) + { + return data()->refattrlist(theID); + } /// Returns the reference attribute by the identifier inline std::shared_ptr reference(const std::string& theID) { diff --git a/src/ModuleBase/ModuleBase_WidgetOptionalBox.cpp b/src/ModuleBase/ModuleBase_WidgetOptionalBox.cpp index edfd91b3f..fd2d845fb 100644 --- a/src/ModuleBase/ModuleBase_WidgetOptionalBox.cpp +++ b/src/ModuleBase/ModuleBase_WidgetOptionalBox.cpp @@ -54,6 +54,7 @@ ModuleBase_WidgetOptionalBox::ModuleBase_WidgetOptionalBox(QWidget* theParent, myHaveFrame = theData->getBooleanAttribute("has_frame", true); myEnableOnCheck = theData->getBooleanAttribute("enable_on_check", true); + myAlwaysShowTitle = theData->getBooleanAttribute("show_title", false); bool isChecked = theData->getBooleanAttribute(ATTR_DEFAULT, false); setDefaultValue(isChecked ? "true" : "false"); @@ -201,7 +202,10 @@ void ModuleBase_WidgetOptionalBox::createControl(const OptionType& theType) myCheckBoxLayout = new QHBoxLayout(myCheckBoxFrame); ModuleBase_Tools::adjustMargins(myCheckBoxLayout); - myCheckBox = new QCheckBox(myCheckBoxFrame); + if (myAlwaysShowTitle) + myCheckBox = new QCheckBox(translate(myGroupTitle), myCheckBoxFrame); + else + myCheckBox = new QCheckBox(myCheckBoxFrame); myCheckBox->setChecked(getDefaultValue() == "true"); myCheckBoxLayout->addWidget(myCheckBox); diff --git a/src/ModuleBase/ModuleBase_WidgetOptionalBox.h b/src/ModuleBase/ModuleBase_WidgetOptionalBox.h index d5ecaf266..94fce5da1 100644 --- a/src/ModuleBase/ModuleBase_WidgetOptionalBox.h +++ b/src/ModuleBase/ModuleBase_WidgetOptionalBox.h @@ -136,6 +136,7 @@ private: bool myHaveFrame; bool myEnableOnCheck; + bool myAlwaysShowTitle; }; #endif /* ModuleBase_WidgetOptionalBox_H_ */ diff --git a/src/SketchAPI/SketchAPI.i b/src/SketchAPI/SketchAPI.i index 6d113b794..ba30a72da 100644 --- a/src/SketchAPI/SketchAPI.i +++ b/src/SketchAPI/SketchAPI.i @@ -51,6 +51,8 @@ %feature("kwargs") SketchAPI_Ellipse::construction; %feature("kwargs") SketchAPI_EllipticArc::construction; %feature("kwargs") SketchAPI_Sketch::addSpline; +%feature("kwargs") SketchAPI_Sketch::addInterpolation; +%feature("kwargs") SketchAPI_Sketch::addApproximation; %feature("kwargs") SketchAPI_Sketch::setAngle; // shared pointers diff --git a/src/SketchAPI/SketchAPI_Sketch.cpp b/src/SketchAPI/SketchAPI_Sketch.cpp index 6ce7550f0..c713dfd97 100644 --- a/src/SketchAPI/SketchAPI_Sketch.cpp +++ b/src/SketchAPI/SketchAPI_Sketch.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -793,6 +794,68 @@ std::shared_ptr SketchAPI_Sketch::addSpline( return aBSpline; } +//-------------------------------------------------------------------------------------- +static std::shared_ptr buildInterpolation( + const CompositeFeaturePtr& theSketch, + const FeaturePtr& theCurveFittingFeature, + const std::list& points, + const bool periodic, + const bool closed) +{ + AttributeBooleanPtr aPeriodicAttr = + theCurveFittingFeature->boolean(SketchPlugin_CurveFitting::PERIODIC_ID()); + fillAttribute(periodic, aPeriodicAttr); + AttributeBooleanPtr aClosedAttr = + theCurveFittingFeature->boolean(SketchPlugin_CurveFitting::CLOSED_ID()); + fillAttribute(closed, aClosedAttr); + AttributeRefAttrListPtr aPointsAttr = + theCurveFittingFeature->refattrlist(SketchPlugin_CurveFitting::POINTS_ID()); + fillAttribute(points, aPointsAttr); + apply(); // to execute and kill the macro-feature + + // find created B-spline feature + BSplinePtr aBSpline; + const std::string& aKindToFind = + periodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID(); + int aNbSubs = theSketch->numberOfSubs(); + for (int anIndex = aNbSubs - 1; anIndex >= 0; --anIndex) { + FeaturePtr aFeature = theSketch->subFeature(anIndex); + if (aFeature->getKind() == aKindToFind) { + aBSpline.reset(periodic ? new SketchAPI_BSplinePeriodic(aFeature) + : new SketchAPI_BSpline(aFeature)); + aBSpline->execute(); + break; + } + } + return aBSpline; +} + +std::shared_ptr SketchAPI_Sketch::addInterpolation( + const std::list& points, + const bool periodic, + const bool closed) +{ + CompositeFeaturePtr aSketch = compositeFeature(); + FeaturePtr anInterpFeature = aSketch->addFeature(SketchPlugin_CurveFitting::ID()); + anInterpFeature->string(SketchPlugin_CurveFitting::TYPE_ID()) + ->setValue(SketchPlugin_CurveFitting::TYPE_INTERPOLATION_ID()); + return buildInterpolation(aSketch, anInterpFeature, points, periodic, closed); +} + +std::shared_ptr SketchAPI_Sketch::addApproximation( + const std::list& points, + const ModelHighAPI_Double& precision, + const bool periodic, + const bool closed) +{ + CompositeFeaturePtr aSketch = compositeFeature(); + FeaturePtr anInterpFeature = aSketch->addFeature(SketchPlugin_CurveFitting::ID()); + anInterpFeature->string(SketchPlugin_CurveFitting::TYPE_ID()) + ->setValue(SketchPlugin_CurveFitting::TYPE_APPROXIMATION_ID()); + fillAttribute(precision, anInterpFeature->real(SketchPlugin_CurveFitting::PRECISION_ID())); + return buildInterpolation(aSketch, anInterpFeature, points, periodic, closed); +} + //-------------------------------------------------------------------------------------- std::shared_ptr SketchAPI_Sketch::addProjection( const ModelHighAPI_Selection & theExternalFeature, diff --git a/src/SketchAPI/SketchAPI_Sketch.h b/src/SketchAPI/SketchAPI_Sketch.h index 11b6ed91f..30674f4fb 100644 --- a/src/SketchAPI/SketchAPI_Sketch.h +++ b/src/SketchAPI/SketchAPI_Sketch.h @@ -28,13 +28,13 @@ #include #include +#include #include #include #include //-------------------------------------------------------------------------------------- class ModelAPI_CompositeFeature; class ModelAPI_Object; -class ModelHighAPI_Double; class ModelHighAPI_Integer; class ModelHighAPI_RefAttr; class ModelHighAPI_Reference; @@ -338,6 +338,21 @@ public: const std::list& multiplicities = std::list(), const bool periodic = false); + /// Add interpolation feature + SKETCHAPI_EXPORT + std::shared_ptr addInterpolation( + const std::list& points, + const bool periodic = false, + const bool closed = false); + + /// Add approximation feature + SKETCHAPI_EXPORT + std::shared_ptr addApproximation( + const std::list& points, + const ModelHighAPI_Double& precision = ModelHighAPI_Double(1.e-3), + const bool periodic = false, + const bool closed = false); + /// Add projection SKETCHAPI_EXPORT std::shared_ptr addProjection( diff --git a/src/SketchPlugin/CMakeLists.txt b/src/SketchPlugin/CMakeLists.txt index 91056a0ed..e441e066f 100644 --- a/src/SketchPlugin/CMakeLists.txt +++ b/src/SketchPlugin/CMakeLists.txt @@ -49,6 +49,7 @@ SET(PROJECT_HEADERS SketchPlugin_ConstraintRigid.h SketchPlugin_ConstraintTangent.h SketchPlugin_ConstraintVertical.h + SketchPlugin_CurveFitting.h SketchPlugin_Ellipse.h SketchPlugin_EllipticArc.h SketchPlugin_ExternalValidator.h @@ -104,6 +105,7 @@ SET(PROJECT_SOURCES SketchPlugin_ConstraintRigid.cpp SketchPlugin_ConstraintTangent.cpp SketchPlugin_ConstraintVertical.cpp + SketchPlugin_CurveFitting.cpp SketchPlugin_Ellipse.cpp SketchPlugin_EllipticArc.cpp SketchPlugin_ExternalValidator.cpp diff --git a/src/SketchPlugin/SketchPlugin_CurveFitting.cpp b/src/SketchPlugin/SketchPlugin_CurveFitting.cpp new file mode 100644 index 000000000..9c16a6eb6 --- /dev/null +++ b/src/SketchPlugin/SketchPlugin_CurveFitting.cpp @@ -0,0 +1,386 @@ +// 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 + +static void convertTo3D(SketchPlugin_Sketch* theSketch, + const AttributeRefAttrListPtr& theAttribute, + bool theClosedButNotPeriodic, + std::list& thePoints); + +static GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch, + AttributeRefAttrListPtr& thePoints, + bool thePeriodic, + bool theClosed); +static GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch, + AttributeRefAttrListPtr& thePoints, + double thePrecision, + bool thePeriodic, + bool theClosed); + + +SketchPlugin_CurveFitting::SketchPlugin_CurveFitting() + : SketchPlugin_SketchEntity() +{ +} + +void SketchPlugin_CurveFitting::initDerivedClassAttributes() +{ + data()->addAttribute(POINTS_ID(), ModelAPI_AttributeRefAttrList::typeId()); + + data()->addAttribute(TYPE_ID(), ModelAPI_AttributeString::typeId()); + + data()->addAttribute(PRECISION_ID(), ModelAPI_AttributeDouble::typeId()); + ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), PRECISION_ID()); + + data()->addAttribute(NEED_CONTROL_POLYGON_ID(), ModelAPI_AttributeBoolean::typeId()); + ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), + NEED_CONTROL_POLYGON_ID()); + + data()->addAttribute(PERIODIC_ID(), ModelAPI_AttributeBoolean::typeId()); + data()->addAttribute(CLOSED_ID(), ModelAPI_AttributeBoolean::typeId()); +} + +void SketchPlugin_CurveFitting::execute() +{ + FeaturePtr aBSpline = createBSplineFeature(); + // create control polygon + AttributeBooleanPtr aNeedControlPoly = boolean(NEED_CONTROL_POLYGON_ID()); + if (aNeedControlPoly && aNeedControlPoly->isInitialized() && aNeedControlPoly->value()) { + bool isPeriodic = boolean(PERIODIC_ID())->value(); + std::list aControlPoles; + SketchPlugin_MacroBSpline::createControlPolygon(aBSpline, isPeriodic, aControlPoles); + } + // constraints for the selected points + createConstraints(aBSpline); +} + +FeaturePtr SketchPlugin_CurveFitting::createBSplineFeature() +{ + // create transient curve if not created yet + if (!myTransientResult) { + getAISObject(AISObjectPtr()); + if (!myTransientResult) + return FeaturePtr(); + } + + SketchPlugin_Sketch* aSketch = sketch(); + if (!aSketch) + return FeaturePtr(); + + bool isPeriodic = boolean(PERIODIC_ID())->value(); + + FeaturePtr aBSplineFeature = aSketch->addFeature( + isPeriodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID()); + + // Convert transient edge to B-spline curve. + GeomCurvePtr aCurve = std::make_shared(myTransientResult); + std::shared_ptr aBSpline = std::make_shared(aCurve); + + // Fill attributes of B-spline feature: + bool aWasBlocked = aBSplineFeature->data()->blockSendAttributeUpdated(true, false); + // 1. Degree + aBSplineFeature->integer(SketchPlugin_BSplineBase::DEGREE_ID())->setValue(aBSpline->degree()); + // 2. Poles + std::list aPoles = aBSpline->poles(); + AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast( + aBSplineFeature->attribute(SketchPlugin_BSplineBase::POLES_ID())); + aPolesAttr->setSize((int)aPoles.size()); + int anIndex = 0; + for (auto it = aPoles.begin(); it != aPoles.end(); ++it, ++anIndex) + aPolesAttr->setPnt(anIndex, aSketch->to2D(*it)); + // 3. Weights + std::list aWeights = aBSpline->weights(); + AttributeDoubleArrayPtr aWeightsAttr = + aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::WEIGHTS_ID()); + if (aWeights.empty()) { + aWeightsAttr->setSize((int)aPoles.size()); + for (anIndex = 0; anIndex < (int)aPoles.size(); ++anIndex) + aWeightsAttr->setValue(anIndex, 1.0); + } + else { + aWeightsAttr->setSize((int)aWeights.size()); + anIndex = 0; + for (auto it = aWeights.begin(); it != aWeights.end(); ++it, ++anIndex) + aWeightsAttr->setValue(anIndex, *it); + } + // 4. Knots (normalized from 0 to 1) + std::list aKnots = aBSpline->knots(); + AttributeDoubleArrayPtr aKnotsAttr = + aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::KNOTS_ID()); + aKnotsAttr->setSize((int)aKnots.size()); + double aFirstKnot = aKnots.front(); + double aLastKnot = aKnots.back(); + anIndex = 0; + for (auto it = aKnots.begin(); it != aKnots.end(); ++it, ++anIndex) + aKnotsAttr->setValue(anIndex, (*it - aFirstKnot) / (aLastKnot - aFirstKnot)); + // 5. Multiplicities + std::list aMults = aBSpline->mults(); + AttributeIntArrayPtr aMultsAttr = + aBSplineFeature->data()->intArray(SketchPlugin_BSplineBase::MULTS_ID()); + aMultsAttr->setSize((int)aMults.size()); + anIndex = 0; + for (auto it = aMults.begin(); it != aMults.end(); ++it, ++anIndex) + aMultsAttr->setValue(anIndex, *it); + + if (!isPeriodic) { + AttributePoint2DPtr aStartPoint = std::dynamic_pointer_cast( + aBSplineFeature->attribute(SketchPlugin_BSpline::START_ID())); + aStartPoint->setValue(aPolesAttr->pnt(0)); + + AttributePoint2DPtr aEndPoint = std::dynamic_pointer_cast( + aBSplineFeature->attribute(SketchPlugin_BSpline::END_ID())); + aEndPoint->setValue(aPolesAttr->pnt(aPolesAttr->size() - 1)); + } + + aBSplineFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->setValue( + boolean(AUXILIARY_ID())->value()); + + aBSplineFeature->data()->blockSendAttributeUpdated(aWasBlocked); + + aBSplineFeature->execute(); + + return aBSplineFeature; +} + +void SketchPlugin_CurveFitting::createConstraints(FeaturePtr theProducedFeature) +{ + if (!theProducedFeature) + return; + + SketchPlugin_Sketch* aSketch = sketch(); + ResultPtr aResult = theProducedFeature->lastResult(); + bool isPeriodic = boolean(PERIODIC_ID())->value(); + bool isClosed = boolean(CLOSED_ID())->value(); + bool isApproximation = string(TYPE_ID())->value() == TYPE_APPROXIMATION_ID(); + + AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID()); + std::list > aPointsList = aPointsAttr->list(); + std::list >::iterator aLastIt = --aPointsList.end(); + for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) { + AttributePtr anAttr = it->second; + if (!anAttr) { + // maybe the SketchPoint is selected + FeaturePtr aFeature = ModelAPI_Feature::feature(it->first); + if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID()) + anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID()); + else + continue; + } + + if (!isPeriodic && it == aPointsList.begin()) { + SketchPlugin_Tools::createConstraintAttrAttr(aSketch, + SketchPlugin_ConstraintCoincidence::ID(), + anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::START_ID())); + if (isClosed) { + // end of B-spline curve should be coincident with the first selected point + SketchPlugin_Tools::createConstraintAttrAttr(aSketch, + SketchPlugin_ConstraintCoincidence::ID(), + anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID())); + } + } + else if (!isPeriodic && !isClosed && it == aLastIt) { + SketchPlugin_Tools::createConstraintAttrAttr(aSketch, + SketchPlugin_ConstraintCoincidence::ID(), + anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID())); + } + else if (!isApproximation) { + SketchPlugin_Tools::createConstraintAttrObject(aSketch, + SketchPlugin_ConstraintCoincidence::ID(), + anAttr, aResult); + } + } +} + +bool SketchPlugin_CurveFitting::customAction(const std::string& theActionId) +{ + bool isOk = true; + if (theActionId == REORDER_POINTS_ACTION_ID()) { + reorderPoints(); + } else { + static const std::string MESSAGE("Error: Feature \"%1\" does not support action \"%2\"."); + Events_InfoMessage("SketchPlugin_CurveFitting", MESSAGE) + .arg(getKind()).arg(theActionId).send(); + isOk = false; + } + return isOk; +} + + +void SketchPlugin_CurveFitting::reorderPoints() +{ + AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID()); + bool isPeriodic = boolean(PERIODIC_ID())->value(); + bool isClosed = boolean(CLOSED_ID())->value(); + + std::list aCoordinates; + convertTo3D(sketch(), aPointsAttr, !isPeriodic && isClosed, aCoordinates); + + // to keep mapping between points and attributes + std::map > aMap; + std::list > aPointsList = aPointsAttr->list(); + bool isPointAdded = aCoordinates.size() != aPointsList.size(); + std::list::iterator aCoordIt = aCoordinates.begin(); + std::list >::iterator anAttrIt = aPointsList.begin(); + for (; aCoordIt != aCoordinates.end() && anAttrIt != aPointsList.end(); ++aCoordIt, ++anAttrIt) + aMap[*aCoordIt] = *anAttrIt; + + // reorder points + GeomAlgoAPI_CurveBuilder::reorderPoints(aCoordinates); + + // re-compose the attribute + bool aWasBlocked = data()->blockSendAttributeUpdated(true); + aPointsAttr->clear(); + for (aCoordIt = aCoordinates.begin(); aCoordIt != aCoordinates.end(); ++aCoordIt) { + const std::pair& aValue = aMap.at(*aCoordIt); + if (aValue.second) + aPointsAttr->append(aValue.second); + else + aPointsAttr->append(aValue.first); + } + data()->blockSendAttributeUpdated(aWasBlocked); +} + +AISObjectPtr SketchPlugin_CurveFitting::getAISObject(AISObjectPtr thePrevious) +{ + SketchPlugin_Sketch* aSketch = sketch(); + if (!aSketch) + return AISObjectPtr(); + + std::string aType = string(TYPE_ID())->value(); + if (aType == TYPE_INTERPOLATION_ID()) { + myTransientResult = buildInterpolationCurve(aSketch, + refattrlist(POINTS_ID()), + boolean(PERIODIC_ID())->value(), + boolean(CLOSED_ID())->value()); + } + else if (aType == TYPE_APPROXIMATION_ID()) { + myTransientResult = buildApproximationCurve(aSketch, + refattrlist(POINTS_ID()), + real(PRECISION_ID())->value(), + boolean(PERIODIC_ID())->value(), + boolean(CLOSED_ID())->value()); + } + if (!myTransientResult) + return AISObjectPtr(); + + AISObjectPtr anAIS = thePrevious; + if (!anAIS) + anAIS.reset(new GeomAPI_AISObject()); + anAIS->createShape(myTransientResult); + + // Modify attributes + SketchPlugin_Tools::customizeFeaturePrs(anAIS, boolean(AUXILIARY_ID())->value()); + + return anAIS; +} + + +// =============================== Auxiliary functions ==================================== + +void convertTo3D(SketchPlugin_Sketch* theSketch, + const AttributeRefAttrListPtr& theAttribute, + bool theClosedButNotPeriodic, + std::list& thePoints) +{ + std::list > aPointsList = theAttribute->list(); + for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) { + AttributePtr anAttr = it->second; + if (!anAttr) { + // maybe the SketchPoint is selected + FeaturePtr aFeature = ModelAPI_Feature::feature(it->first); + if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID()) + anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID()); + else { + thePoints.clear(); + break; + } + } + + AttributePoint2DPtr aPoint = std::dynamic_pointer_cast(anAttr); + if (aPoint) { + GeomPointPtr aPnt3D = theSketch->to3D(aPoint->x(), aPoint->y()); + thePoints.push_back(aPnt3D); + } + } + + if (theClosedButNotPeriodic && !thePoints.empty() && + thePoints.front()->distance(thePoints.back()) > 1.e-7) + thePoints.push_back(thePoints.front()); // close the curve +} + +GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch, + AttributeRefAttrListPtr& thePoints, + bool thePeriodic, + bool theClosed) +{ + std::list aCoordinates; + convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates); + + GeomEdgePtr aResult; + if (aCoordinates.size() > 1) { + static const bool isReorder = false; + static GeomDirPtr aStartEndDir; + aResult = GeomAlgoAPI_CurveBuilder::edge(aCoordinates, thePeriodic, + isReorder, aStartEndDir, aStartEndDir); + } + return aResult; +} + +GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch, + AttributeRefAttrListPtr& thePoints, + double thePrecision, + bool thePeriodic, + bool theClosed) +{ + std::list aCoordinates; + convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates); + + GeomEdgePtr aResult; + if (aCoordinates.size() > 1) + aResult = GeomAlgoAPI_CurveBuilder::approximate(aCoordinates, thePeriodic, thePrecision); + return aResult; +} diff --git a/src/SketchPlugin/SketchPlugin_CurveFitting.h b/src/SketchPlugin/SketchPlugin_CurveFitting.h new file mode 100644 index 000000000..d7c8b64b1 --- /dev/null +++ b/src/SketchPlugin/SketchPlugin_CurveFitting.h @@ -0,0 +1,154 @@ +// 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_CurveFitting_H_ +#define SketchPlugin_CurveFitting_H_ + +#include +#include + +#include + +class GeomAPI_Edge; + +/**\class SketchPlugin_CurveFitting + * \ingroup Plugins + * \brief Feature for creation of the new B-spline curve in sketch + * as an interpolation or an approximation of a list of points. + */ +class SketchPlugin_CurveFitting : public SketchPlugin_SketchEntity, + public GeomAPI_IPresentable +{ +public: + /// Interpolation macro feature kind + inline static const std::string& ID() + { + static const std::string ID("SketchCurveFitting"); + return ID; + } + + /// list of selected points + inline static const std::string& POINTS_ID() + { + static const std::string ID("points"); + return ID; + } + + /// attribute for the periodic flag + inline static const std::string& PERIODIC_ID() + { + static const std::string ID("periodic"); + return ID; + } + + /// attribute for the closed flag + inline static const std::string& CLOSED_ID() + { + static const std::string ID("closed"); + return ID; + } + + /// attribute for the flag of creation a control polygon + inline static const std::string& NEED_CONTROL_POLYGON_ID() + { + static const std::string ID("need_control_poly"); + return ID; + } + + /// attribute for the type of the operation + inline static const std::string& TYPE_ID() + { + static const std::string ID("type"); + return ID; + } + + /// value for the type of operation + inline static const std::string& TYPE_INTERPOLATION_ID() + { + static const std::string ID("interpolation_type"); + return ID; + } + + /// value for the type of operation + inline static const std::string& TYPE_APPROXIMATION_ID() + { + static const std::string ID("approximation_type"); + return ID; + } + + /// attribute for the precision of the approximation + inline static const std::string& PRECISION_ID() + { + static const std::string ID("precision"); + return ID; + } + + /// attribute for the closed flag + inline static const std::string& REORDER_POINTS_ACTION_ID() + { + static const std::string ID("reorder_points"); + return ID; + } + + /// Returns the kind of a feature + SKETCHPLUGIN_EXPORT virtual const std::string& getKind() + { + static std::string MY_KIND = SketchPlugin_CurveFitting::ID(); + return MY_KIND; + } + + /// Returns the AIS preview + virtual AISObjectPtr getAISObject(AISObjectPtr thePrevious); + + /// Creates a new part document if needed + SKETCHPLUGIN_EXPORT virtual void execute(); + + /// Reimplemented from ModelAPI_Feature::isMacro(). + /// \returns true + SKETCHPLUGIN_EXPORT virtual bool isMacro() const {return true;}; + + SKETCHPLUGIN_EXPORT virtual bool isPreviewNeeded() const {return false;}; + + /// Performs some functionality by action id. + /// \param[in] theAttributeId action key id. + /// \return false in case if action not performed. + SKETCHPLUGIN_EXPORT virtual bool customAction(const std::string& theActionId); + + /// Use plugin manager for features creation + SketchPlugin_CurveFitting(); + +protected: + /// \brief Initializes attributes of derived class. + virtual void initDerivedClassAttributes(); + +private: + /// \brief Create a feature, which passes through the selected points + FeaturePtr createBSplineFeature(); + + /// \brief Create coincidence constraints between selected points and the produced curve. + void createConstraints(FeaturePtr theProducedFeature); + + /// \brief Reorder point to compose the polyline of the minimal length + void reorderPoints(); + +private: + std::shared_ptr myTransientResult; ///< Interpolation curve +}; + +#endif diff --git a/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp b/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp index 48158f1a8..3d3ab4c53 100644 --- a/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp +++ b/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp @@ -84,7 +84,7 @@ void SketchPlugin_MacroBSpline::execute() if (boolean(CONTROL_POLYGON_ID())->value()) { std::list aControlPoles; - createControlPolygon(aBSpline, aControlPoles); + createControlPolygon(aBSpline, myIsPeriodic, aControlPoles); constraintsForPoles(aControlPoles); } } @@ -147,6 +147,7 @@ FeaturePtr SketchPlugin_MacroBSpline::createBSplineFeature() } void SketchPlugin_MacroBSpline::createControlPolygon(FeaturePtr theBSpline, + bool thePeriodic, std::list& thePoles) { AttributePoint2DArrayPtr aPoles = std::dynamic_pointer_cast( @@ -158,7 +159,7 @@ void SketchPlugin_MacroBSpline::createControlPolygon(FeaturePtr theBSpline, // segments for (int index = 1; index < aSize; ++index) createAuxiliarySegment(aPoles, index - 1, index); - if (myIsPeriodic) { + if (thePeriodic) { // additional segment to close the control polygon createAuxiliarySegment(aPoles, aSize - 1, 0); } diff --git a/src/SketchPlugin/SketchPlugin_MacroBSpline.h b/src/SketchPlugin/SketchPlugin_MacroBSpline.h index 9ecf63d49..1c3aca6d7 100644 --- a/src/SketchPlugin/SketchPlugin_MacroBSpline.h +++ b/src/SketchPlugin/SketchPlugin_MacroBSpline.h @@ -108,7 +108,12 @@ protected: private: FeaturePtr createBSplineFeature(); - void createControlPolygon(FeaturePtr theBSpline, std::list& thePoles); + /// Create control polygon for the B-spline and returns the list of its poles + static void createControlPolygon(FeaturePtr theBSpline, + bool thePeriodic, + std::list& thePoles); + + /// Create additional coincidences if other features were selected while creating the B-spline void constraintsForPoles(const std::list& thePoles); /// Create Point feature coincident with the B-spline pole @@ -124,6 +129,7 @@ private: const int thePoleIndex1, const int thePoleIndex2 = -1); friend class SketchPlugin_BSplineBase; + friend class SketchPlugin_CurveFitting; private: std::list myKnots; @@ -133,9 +139,9 @@ private: }; -/**\class SketchPlugin_MacroBSpline +/**\class SketchPlugin_MacroBSplinePeriodic * \ingroup Plugins -* \brief Feature for creation of the new B-spline in Sketch. +* \brief Feature for creation of the new periodic B-spline in Sketch. */ class SketchPlugin_MacroBSplinePeriodic : public SketchPlugin_MacroBSpline { diff --git a/src/SketchPlugin/SketchPlugin_Plugin.cpp b/src/SketchPlugin/SketchPlugin_Plugin.cpp index 3be8ffc56..8ef46386d 100644 --- a/src/SketchPlugin/SketchPlugin_Plugin.cpp +++ b/src/SketchPlugin/SketchPlugin_Plugin.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include @@ -154,6 +155,8 @@ SketchPlugin_Plugin::SketchPlugin_Plugin() new SketchPlugin_MultiRotationAngleValidator); aFactory->registerValidator("SketchPlugin_BSplineValidator", new SketchPlugin_BSplineValidator); + aFactory->registerValidator("SketchPlugin_CurveFittingValidator", + new SketchPlugin_CurveFittingValidator); // register this plugin ModelAPI_Session::get()->registerPlugin(this); @@ -276,6 +279,8 @@ FeaturePtr SketchPlugin_Plugin::createFeature(std::string theFeatureID) return FeaturePtr(new SketchPlugin_EllipticArc); } else if (theFeatureID == SketchPlugin_MacroEllipticArc::ID()) { return FeaturePtr(new SketchPlugin_MacroEllipticArc); + } else if (theFeatureID == SketchPlugin_CurveFitting::ID()) { + return FeaturePtr(new SketchPlugin_CurveFitting); } else if (theFeatureID == SketchPlugin_SketchDrawer::ID()) { return FeaturePtr(new SketchPlugin_SketchDrawer); } else if (theFeatureID == SketchPlugin_SketchCopy::ID()) { @@ -359,6 +364,7 @@ std::shared_ptr SketchPlugin_Plugin aMsg->setState(SketchPlugin_MacroEllipticArc::ID(), aHasSketchPlane); 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_Validators.cpp b/src/SketchPlugin/SketchPlugin_Validators.cpp index f9107bc4f..320315bd2 100644 --- a/src/SketchPlugin/SketchPlugin_Validators.cpp +++ b/src/SketchPlugin/SketchPlugin_Validators.cpp @@ -31,6 +31,7 @@ #include "SketchPlugin_Ellipse.h" #include "SketchPlugin_EllipticArc.h" #include "SketchPlugin_Fillet.h" +#include "SketchPlugin_CurveFitting.h" #include "SketchPlugin_Line.h" #include "SketchPlugin_MacroArc.h" #include "SketchPlugin_MacroCircle.h" @@ -49,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -1894,3 +1896,22 @@ bool SketchPlugin_BSplineValidator::isValid(const AttributePtr& theAttribute, return true; } + +bool SketchPlugin_CurveFittingValidator::isValid(const FeaturePtr& theFeature, + const std::list& theArguments, + Events_InfoMessage& theError) const +{ + AttributeRefAttrListPtr aRefAttrList = + theFeature->refattrlist(SketchPlugin_CurveFitting::POINTS_ID()); + AttributeBooleanPtr aPeriodicAttr = + theFeature->boolean(SketchPlugin_CurveFitting::PERIODIC_ID()); + + // check number of selected entities + int aMinNbPoints = aPeriodicAttr->value() ? 3 : 2; + if (aRefAttrList->size() < aMinNbPoints) { + theError = "Not enough points selected. Need at least %1 points."; + theError.arg(aMinNbPoints); + return false; + } + return true; +} diff --git a/src/SketchPlugin/SketchPlugin_Validators.h b/src/SketchPlugin/SketchPlugin_Validators.h index 1b20c45fd..3ef50dd33 100644 --- a/src/SketchPlugin/SketchPlugin_Validators.h +++ b/src/SketchPlugin/SketchPlugin_Validators.h @@ -546,4 +546,20 @@ class SketchPlugin_BSplineValidator : public ModelAPI_AttributeValidator Events_InfoMessage& theError) const; }; +/**\class SketchPlugin_CurveFittingValidator + * \ingroup Validators + * \brief Validator for the selected vertices for the curve fitting feature. + */ +class SketchPlugin_CurveFittingValidator : public ModelAPI_FeatureValidator +{ +public: + //! returns true if number of selected points is greater than the minimal value + //! \param theAttribute the checked attribute + //! \param theArguments arguments of the attribute + //! \param theError error message + virtual bool isValid(const std::shared_ptr& theFeature, + const std::list& theArguments, + Events_InfoMessage& theError) const; +}; + #endif diff --git a/src/SketchPlugin/SketchPlugin_msg_en.ts b/src/SketchPlugin/SketchPlugin_msg_en.ts index 5dd2eaa01..aad453f54 100644 --- a/src/SketchPlugin/SketchPlugin_msg_en.ts +++ b/src/SketchPlugin/SketchPlugin_msg_en.ts @@ -1,6 +1,15 @@ + + + workshop + + Curve fitting + Curve fitting + + + Sketch:Model_FeatureValidator @@ -2262,4 +2271,121 @@ + + + + SketchCurveFitting + + Curve fitting + Curve fitting + + + Create curve passing through the points + Create curve passing through the points + + + Error: Feature "%1" does not support action "%2". + Error: Feature "%1" does not support action "%2". + + + + SketchCurveFitting:SketchPlugin_CurveFittingValidator + + Not enough points selected. Need at least %1 points. + Not enough points selected. Need at least %1 points. + + + + SketchCurveFitting:points + + Points + Points + + + Select points for curve fitting + Select points for curve fitting + + + Attribute "%1" is not initialized. + Select points for interpolation + + + + SketchCurveFitting:periodic + + Periodic + Periodic + + + Make curve periodic + Make curve periodic + + + + SketchCurveFitting:closed + + Closed + Closed + + + Make curve closed, but not periodic + Make curve closed, but not periodic + + + + SketchCurveFitting:type + + Interpolation + Interpolation + + + Approximation + Approximation + + + + SketchCurveFitting:precision + + Precision + Precision + + + Maximal distance from selected points to the curve + Maximal distance from selected points to the curve + + + + SketchCurveFitting:need_control_poly + + Create control polygon + Create control polygon + + + Specify if the control polygon should be created + Specify if the control polygon should be created + + + + SketchCurveFitting:reorder_points + + Reorder points + Reorder points + + + Sort selected points to minimize the distance heighbors + Sort selected points to minimize the distance heighbors + + + + SketchCurveFitting:Auxiliary + + Auxiliary + Auxiliary + + + Construction element + Construction element + + + diff --git a/src/SketchPlugin/SketchPlugin_msg_fr.ts b/src/SketchPlugin/SketchPlugin_msg_fr.ts index 86f682282..d509191c4 100644 --- a/src/SketchPlugin/SketchPlugin_msg_fr.ts +++ b/src/SketchPlugin/SketchPlugin_msg_fr.ts @@ -72,6 +72,10 @@ Horizontal Distance Distance horizontale + + Curve fitting + Courbe d'ajustement + Length Longueur @@ -4290,4 +4294,120 @@ + + + SketchCurveFitting + + Curve fitting + Courbe d'ajustement + + + Create curve passing through the points + Créer une courbe passant par les points + + + Error: Feature "%1" does not support action "%2". + Erreur: la fonctionnalité "% 1" ne prend pas en charge l'action "% 2". + + + + SketchCurveFitting:SketchPlugin_CurveFittingValidator + + Not enough points selected. Need at least %1 points. + Pas assez de points sélectionnés. Besoin d'au moins %1 points. + + + + SketchCurveFitting:points + + Points + Points + + + Select points for curve fitting + Sélectionner des points pour l'ajustement de courbe + + + Attribute "%1" is not initialized. + Sélectionner des points pour l'ajustement de courbe + + + + SketchCurveFitting:periodic + + Periodic + Périodique + + + Make curve periodic + Rendre la courbe périodique + + + + SketchCurveFitting:closed + + Closed + Fermé + + + Make curve closed, but not periodic + Rendre la courbe fermée, mais pas périodique + + + + SketchCurveFitting:type + + Interpolation + Interpolation + + + Approximation + Approximation + + + + SketchCurveFitting:precision + + Precision + Précision + + + Maximal distance from selected points to the curve + Distance maximale entre les points sélectionnés et la courbe + + + + SketchCurveFitting:need_control_poly + + Create control polygon + Créer un polygone de contrôle + + + Specify if the control polygon should be created + Précisez si le polygone de contrôle doit être créé + + + + SketchCurveFitting:reorder_points + + Reorder points + Réorganiser les points + + + Sort selected points to minimize the distance heighbors + Trier les points sélectionnés pour minimiser la distance des voisins + + + + SketchCurveFitting:Auxiliary + + Auxiliary + Auxiliaire + + + Construction element + Élément de construction + + + diff --git a/src/SketchPlugin/doc/TUI_curvefitting.rst b/src/SketchPlugin/doc/TUI_curvefitting.rst new file mode 100644 index 000000000..1c71914a4 --- /dev/null +++ b/src/SketchPlugin/doc/TUI_curvefitting.rst @@ -0,0 +1,23 @@ + + .. _tui_create_interpolation: + +Create interpolation curve +========================== + +.. literalinclude:: examples/interpolation.py + :linenos: + :language: python + +:download:`Download this script ` + + + .. _tui_create_approximation: + +Create approximation curve +========================== + +.. literalinclude:: examples/approximation.py + :linenos: + :language: python + +:download:`Download this script ` diff --git a/src/SketchPlugin/doc/curveFittingFeature.rst b/src/SketchPlugin/doc/curveFittingFeature.rst new file mode 100644 index 000000000..11376117b --- /dev/null +++ b/src/SketchPlugin/doc/curveFittingFeature.rst @@ -0,0 +1,89 @@ +.. |curvefitting.icon| image:: images/curvefitting.png + +Curve Fitting +============= + +The Curve Fitting is a tool to create a curve by a set of given points. + +To start this operation: + +#. select *Sketch - > Curve fitting* item in the Main Menu or +#. click |curvefitting.icon| **Curve fitting** button in the Sketch toolbar: + +There are 2 algorithms for the curve creation: + +- **Interpolation** - the curve passes exactly through the selected points; +- **Approximation** - curve passes near the selected points to some extent. + +Both algorithms have additional options: + +- **Periodic** - forces the created curve to be smooth periodic curve; +- **Closed** - produces closed, but non-periodic curve. As a result, it has the same start and end points, but it may be connected non-smoothly there; +- **Create control polygon** - if the fitting curve is a B-spline curve, this option will create it's control polygon. + + +Interpolation +""""""""""""" + +.. image:: images/curvefitting_panel_interpolation.png + :align: center + +Select the list of points to create a curve. The curve will pass directly through these points in the given order. + +Clicking the **Reorder points** button will change the order of selected points to minimize the distances between the neighbors. + +**TUI Command**: + +.. py:function:: Sketch_1.addInterpolation(points, periodic = False, closed = False) + + :param list: points for the curve. + :param boolean: flag to make the curve periodic. + :param boolean: flag to make the curve closed but not periodic. + :return: Created B-spline curve. + +Result +"""""" + +The created B-spline curve appears in the view. + +.. image:: images/curvefitting_interpolation_res.png + :align: center + +.. centered:: + Interpolation curve (non-closed, periodic and closed) + +**See Also** a sample TUI Script of :ref:`tui_create_interpolation` operation. + + +Approximation +""""""""""""" + +.. image:: images/curvefitting_panel_approximation.png + :align: center + +Select the list of points to create a curve and set the **Precision** value. The curve will pass not far than the precision of these points. + +Clicking the **Reorder points** button will change the order of selected points to minimize the distances between the neighbors. + +**TUI Command**: + +.. py:function:: Sketch_1.addApproximation(points, precision = 0.001, periodic = False, closed = False) + + :param list: points for the curve. + :param double: how close the curve should pass according to the points. + :param boolean: flag to make the curve periodic. + :param boolean: flag to make the curve closed but not periodic. + :return: Created B-spline curve. + +Result +"""""" + +The created B-spline curve appears in the view. + +.. image:: images/curvefitting_approximation_res.png + :align: center + +.. centered:: + Approximated curve (non-closed, periodic and closed) + +**See Also** a sample TUI Script of :ref:`tui_create_approximation` operation. diff --git a/src/SketchPlugin/doc/examples/approximation.py b/src/SketchPlugin/doc/examples/approximation.py new file mode 100644 index 000000000..f2725583a --- /dev/null +++ b/src/SketchPlugin/doc/examples/approximation.py @@ -0,0 +1,38 @@ +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Sketch +Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY")) + +### Create SketchPoints +SketchPoint_1 = Sketch_1.addPoint(-70.28350515463917, 1.388316151202744) +SketchPoint_2 = Sketch_1.addPoint(-26.89862542955327, 51.7147766323024) +SketchPoint_3 = Sketch_1.addPoint(40.08762886597938, 32.27835051546391) +SketchPoint_4 = Sketch_1.addPoint(66.46563573883162, -29.8487972508591) + +### Create approximating curve +ApproximationPoints = [SketchPoint_1.coordinates(), + SketchPoint_2.coordinates(), + SketchPoint_3.coordinates(), + SketchPoint_4.coordinates()] +SketchBSpline_1 = Sketch_1.addApproximation(ApproximationPoints, precision = 30) +Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_1.startPoint()) +Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_1.endPoint()) + +### Create periodic approximating curve +SketchBSpline_2 = Sketch_1.addApproximation(ApproximationPoints, precision = 30, periodic = True) + +### Create closed approximating curve +SketchBSpline_3 = Sketch_1.addApproximation(ApproximationPoints, precision = 30, closed = True) +Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.startPoint()) +Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.endPoint()) + +model.do() + +model.end() diff --git a/src/SketchPlugin/doc/examples/interpolation.py b/src/SketchPlugin/doc/examples/interpolation.py new file mode 100644 index 000000000..fc32ac4be --- /dev/null +++ b/src/SketchPlugin/doc/examples/interpolation.py @@ -0,0 +1,47 @@ +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() + +### Create Part +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() + +### Create Sketch +Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY")) + +### Create SketchPoints +SketchPoint_1 = Sketch_1.addPoint(-70.28350515463917, 1.388316151202744) +SketchPoint_2 = Sketch_1.addPoint(-26.89862542955327, 51.7147766323024) +SketchPoint_3 = Sketch_1.addPoint(40.08762886597938, 32.27835051546391) +SketchPoint_4 = Sketch_1.addPoint(66.46563573883162, -29.8487972508591) + +### Create interpolation curve +InterpolationPoints = [SketchPoint_1.coordinates(), + SketchPoint_2.coordinates(), + SketchPoint_3.coordinates(), + SketchPoint_4.coordinates()] +SketchBSpline_1 = Sketch_1.addInterpolation(InterpolationPoints) +Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_1.startPoint()) +Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_1.result()) +Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_1.result()) +Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_1.endPoint()) + +### Create periodic interpolation curve +SketchBSpline_2 = Sketch_1.addInterpolation(InterpolationPoints, periodic = True) +Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_2.result()) +Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_2.result()) +Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_2.result()) +Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_2.result()) + +### Create closed interpolation curve +SketchBSpline_3 = Sketch_1.addInterpolation(InterpolationPoints, closed = True) +Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.startPoint()) +Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_3.result()) +Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_3.result()) +Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_3.result()) +Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.endPoint()) + +model.do() + +model.end() diff --git a/src/SketchPlugin/doc/images/curvefitting.png b/src/SketchPlugin/doc/images/curvefitting.png new file mode 100644 index 0000000000000000000000000000000000000000..dea1f22ad726a31c9c67e36273a54f86bc225a15 GIT binary patch literal 464 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 literal 0 HcmV?d00001 diff --git a/src/SketchPlugin/icons/curvefitting.png b/src/SketchPlugin/icons/curvefitting.png new file mode 100644 index 0000000000000000000000000000000000000000..dea1f22ad726a31c9c67e36273a54f86bc225a15 GIT binary patch literal 464 zcmV;>0WbcEP)M#s4MDZxson* zDH%u^Y$nAfx+{}EKo^T#$Y8iK8Em?N!CRf_io5Uku{bZ6E8k~+KA-1R@Qgw%5mz*e zsH!C$@)fAK3`#c&V#pd_fm8j(1ipf3Pp@kh#@|#bVI=82!e&4p3K-pf%IA*%E$A4U zh)9!JN0|l6Saty#3JS4A+@gE$;=);#>r?0lB0xnU^?a7Q1(15MeRL{ntexK%U1&o> z0MEc2D9X7rZuKR9cvZ{)?qGU4=qgn=aCQkHus_@_P!eMY9#4*n^##99()C*10bKzU zG;@P2BisXbVU^@x@Ye{PBa_1-asiYPGxn4}xNNkWKRYmDQlGLd9~dB1h$Z4B+)NcH zI`^>XLjy*|Wm+NB0St;+^W^#U*CMF5KrQLoww+4aSMULd@_1fSlkG$R0000 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp index f69cd48b5..db330f5dd 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp @@ -22,65 +22,38 @@ #include #include -#include #include namespace GCS { -DeriVector2 BSplineImpl::Value(double u, double du, double* derivparam) -{ - if (!isCacheValid()) - rebuildCache(); - - std::shared_ptr aValue; - std::shared_ptr aDeriv; - myCurve->D1(u, aValue, aDeriv); - - // calculate the derivative on solver's parameter - std::shared_ptr aValueDeriv(new GeomAPI_Pnt2d(0.0, 0.0)); - bool hasParam = false; - std::list aPolesDeriv; - for (GCS::VEC_P::iterator anIt = poles.begin(); anIt != poles.end(); ++anIt) { - double x = 0.0, y = 0.0; - if (anIt->x == derivparam) { - x = 1.0; - hasParam = true; - } - else if (anIt->y == derivparam) { - y = 1.0; - hasParam = true; + static void periodicNormalization(double& theParam, double thePeriodStart, double thePeriodEnd) + { + double aPeriod = thePeriodEnd - thePeriodStart; + if (aPeriod > tolerance) { + theParam = std::max(thePeriodStart, + theParam + aPeriod * std::ceil((thePeriodStart - theParam) / aPeriod)); } - aPolesDeriv.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(x, y))); - } - if (hasParam) { - // use non-periodic curve, because the most of B-spline coefficients are 0, - // thus, it is not necessary to keep original knots and multiplicities to get correct value - std::shared_ptr aCurveDeriv( - new GeomAPI_BSpline2d(degree, aPolesDeriv, myCachedWeights)); - aCurveDeriv->D0(u, aValueDeriv); } - return DeriVector2(aValue->x(), aValue->y(), - aValueDeriv->x() + aDeriv->x() * du, aValueDeriv->y() + aDeriv->y() * du); + +DeriVector2 BSplineImpl::Value(double u, double du, double* derivparam) +{ + DeriVector2 value, deriv; + d1(u, derivparam, value, deriv); + return value.sum(GCS::DeriVector2(0., 0., deriv.x, deriv.y).mult(du)); } DeriVector2 BSplineImpl::CalculateNormal(Point &p, double* derivparam) { - if (!isCacheValid()) - rebuildCache(); - double u = 0.0; - if (!myCurve->parameter(GeomPnt2dPtr(new GeomAPI_Pnt2d(*p.x, *p.y)), 1e100, u)) + if (!parameter(p, u)) return DeriVector2(); - std::shared_ptr aValue; - std::shared_ptr aDeriv; - myCurve->D1(u, aValue, aDeriv); - - DeriVector2 norm(aDeriv->x(), aDeriv->y(), 0.0, 0.0); - return norm.rotate90ccw(); + DeriVector2 value, deriv; + d1(u, derivparam, value, deriv); + return deriv.rotate90ccw(); } BSplineImpl* BSplineImpl::Copy() @@ -88,49 +61,120 @@ BSplineImpl* BSplineImpl::Copy() return new BSplineImpl(*this); } +void BSplineImpl::d1(double theU, + double* theDerivParam, + GCS::DeriVector2& theValue, + GCS::DeriVector2& theDerivative) +{ + int aSpan = spanIndex(theU); + std::vector aPoles; + std::vector aWeights; + spanPolesAndWeights(aSpan, theDerivParam, aPoles, aWeights); + performDeBoor(theU, aSpan, aPoles, aWeights, theValue, theDerivative); +} -bool BSplineImpl::isCacheValid() const +int BSplineImpl::spanIndex(double& u) { - // curve has to be initialized - bool isValid = myCurve.get() && !myCurve->isNull(); - - static const double THE_TOLERANCE = 1.e-7; - // compare poles - isValid = isValid && poles.size() == myCachedPoles.size(); - std::list::const_iterator aCachePIt = myCachedPoles.begin(); - GCS::VEC_P::const_iterator aPolesIt = poles.begin(); - for (; isValid && aPolesIt != poles.end(); ++aPolesIt, ++aCachePIt) { - isValid = isValid && fabs((*aCachePIt)->x() - *aPolesIt->x) < THE_TOLERANCE - && fabs((*aCachePIt)->y() - *aPolesIt->y) < THE_TOLERANCE; + if (myFlatKnots.empty()) { + // fill flat knots indices + for (int i = 0; i < (int)mult.size(); ++i) + myFlatKnots.resize(myFlatKnots.size() + mult[i], *knots[i]); + if (periodic) { + // additional knots at the beginning and the end to complete periodity + int anExtraBegin = degree + 1 - mult.front(); + int anExtraEnd = degree + 1 - mult.back(); + double aPeriod = *knots.back() - *knots.front(); + VEC_D aNewFlatKnots; + aNewFlatKnots.reserve(myFlatKnots.size() + (size_t)(anExtraBegin + anExtraEnd)); + auto it = myFlatKnots.end() - mult.back() - anExtraBegin; + while (anExtraBegin > 0) { + aNewFlatKnots.push_back(*(it++) - aPeriod); + --anExtraBegin; + } + aNewFlatKnots.insert(aNewFlatKnots.end(), myFlatKnots.begin(), myFlatKnots.end()); + it = myFlatKnots.begin() + mult.front(); + while (anExtraEnd > 0) { + aNewFlatKnots.push_back(*(it++) + aPeriod); + --anExtraEnd; + } + myFlatKnots = aNewFlatKnots; + } } - // compare weights - isValid = isValid && weights.size() == myCachedWeights.size(); - std::list::const_iterator aCacheWIt = myCachedWeights.begin(); - GCS::VEC_pD::const_iterator aWeightsIt = weights.begin(); - for (; isValid && aWeightsIt != weights.end(); ++aWeightsIt, ++aCacheWIt) - isValid = isValid && fabs(*aCacheWIt - **aWeightsIt) < THE_TOLERANCE; + if (periodic) + periodicNormalization(u, *knots.front(), *knots.back()); + + int anIndex = 0; + for (int i = 1; i < (int)knots.size() - 1; ++i) { + if (u <= *knots[i]) + break; + anIndex += mult[i]; + } + return anIndex; +} - return isValid; +void BSplineImpl::spanPolesAndWeights(int theSpanIndex, + double* theDerivParam, + std::vector& thePoles, + std::vector& theWeights) const +{ + thePoles.reserve(degree + 1); + theWeights.reserve(degree + 1); + for (int i = theSpanIndex; i <= theSpanIndex + degree; ++i) { + // optimization: weighted pole + int idx = i % (int)poles.size(); + thePoles.push_back(GCS::DeriVector2(poles[idx], theDerivParam).mult(*weights[idx])); + theWeights.push_back(*weights[idx]); + } +} + +void BSplineImpl::performDeBoor(double theU, + int theSpanIndex, + std::vector& thePoles, + std::vector& theWeights, + GCS::DeriVector2& theValue, + GCS::DeriVector2& theDerivative) const +{ + std::vector aPDeriv(thePoles.size(), DeriVector2()); + std::vector aWDeriv(theWeights.size(), 0.0); + for (int i = 0; i < degree; ++i) { + for (int j = degree; j > i; --j) { + double denom = (myFlatKnots[theSpanIndex + j + degree - i] - + myFlatKnots[theSpanIndex + j]); + double a = (theU - myFlatKnots[theSpanIndex + j]) / denom; + aPDeriv[j] = aPDeriv[j].linCombi(a, aPDeriv[j - 1], 1.0 - a).sum( + thePoles[j].subtr(thePoles[j - 1]).mult(1.0 / denom)); + aWDeriv[j] = aWDeriv[j] * a + aWDeriv[j - 1] * (1.0 - a) + + (theWeights[j] - theWeights[j - 1]) / denom; + thePoles[j] = thePoles[j].linCombi(a, thePoles[j - 1], 1.0 - a); + theWeights[j] = theWeights[j] * a + theWeights[j - 1] * (1.0 - a); + } + } + double w = 1 / theWeights[degree]; + theValue = thePoles[degree].mult(w); + theDerivative = aPDeriv[degree].subtr(theValue.mult(aWDeriv[degree])).mult(w); } -void BSplineImpl::rebuildCache() +bool BSplineImpl::parameter(const Point& thePoint, double& theParam) const { - myCachedPoles.clear(); - myCachedWeights.clear(); - myCachedKnots.clear(); - myCachedMultiplicities.clear(); - - for (GCS::VEC_P::iterator anIt = poles.begin(); anIt != poles.end(); ++anIt) - myCachedPoles.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(*anIt->x, *anIt->y))); - for (GCS::VEC_pD::iterator anIt = weights.begin(); anIt != weights.end(); ++anIt) - myCachedWeights.push_back(**anIt); - for (GCS::VEC_pD::iterator anIt = knots.begin(); anIt != knots.end(); ++anIt) - myCachedKnots.push_back(**anIt); - myCachedMultiplicities.assign(mult.begin(), mult.end()); - - myCurve.reset(new GeomAPI_BSpline2d(degree, myCachedPoles, myCachedWeights, - myCachedKnots, myCachedMultiplicities, periodic)); + std::list aPoles; + std::list aWeights; + std::list aKnots; + std::list aMults; + + for (GCS::VEC_P::const_iterator anIt = poles.begin(); anIt != poles.end(); ++anIt) + aPoles.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(*anIt->x, *anIt->y))); + for (GCS::VEC_pD::const_iterator anIt = weights.begin(); anIt != weights.end(); ++anIt) + aWeights.push_back(**anIt); + for (GCS::VEC_pD::const_iterator anIt = knots.begin(); anIt != knots.end(); ++anIt) + aKnots.push_back(**anIt); + aMults.assign(mult.begin(), mult.end()); + + std::shared_ptr aCurve( + new GeomAPI_BSpline2d(degree, aPoles, aWeights, aKnots, aMults, periodic)); + + GeomPnt2dPtr aPoint(new GeomAPI_Pnt2d(*thePoint.x, *thePoint.y)); + return aCurve->parameter(aPoint, 1e100, theParam); } } // namespace GCS diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h index c83e1a9dd..3b734538f 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h @@ -22,12 +22,6 @@ #include -#include -#include - -class GeomAPI_BSpline2d; -class GeomAPI_Pnt2d; - namespace GCS { /// \brife SHAPER's implementation of B-spline curves in PlaneGCS solver class BSplineImpl : public BSpline @@ -39,17 +33,32 @@ namespace GCS { virtual BSplineImpl* Copy(); private: - /// Verify the cached curve satisfies to the parameters - bool isCacheValid() const; - /// Poles or weights are changed, cache curve has to be rebuilt - void rebuildCache(); + /// Return the index of start knot for the given parameter. + /// Parameter is updated accordingly, if the B-spline curve is periodic + /// and the parameter is out of period. + int spanIndex(double& u); + + /// Collect the list of poles and their weights affecting the given span + void spanPolesAndWeights(int theSpanIndex, + double* theDerivParam, + std::vector& thePoles, + std::vector& theWeights) const; + + /// Execute De Boor algorithm to calculate B-spline curve's value + void performDeBoor(double theU, int theSpanIndex, + std::vector& thePoles, std::vector& theWeights, + GCS::DeriVector2& theValue, GCS::DeriVector2& theDerivative) const; + + /// Calculate the value and the first derivative for the given parameter on B-spline + void d1(double theU, double* theDerivParam, + GCS::DeriVector2& theValue, GCS::DeriVector2& theDerivative); + + /// Find the parameter on B-spline corresponding to the given point + /// \return \c false if it is unable to calculate the parameter + bool parameter(const Point& thePoint, double& theParam) const; private: - std::shared_ptr myCurve; /// cached B-spline curve - std::list > myCachedPoles; /// cached B-spline poles - std::list myCachedWeights; /// cached B-spline weights - std::list myCachedKnots; /// cached B-spline knots - std::list myCachedMultiplicities; /// cached B-spline multiplicities + VEC_D myFlatKnots; /// indices of knots duplicated by multiplicity }; } -- 2.39.2