From c0c5763cf0cc9462b9bca9d1d272f862d921f130 Mon Sep 17 00:00:00 2001 From: azv Date: Wed, 5 Feb 2020 15:07:42 +0300 Subject: [PATCH] Unit test for adding pole to B-spline curve --- src/SketchAPI/SketchAPI_BSpline.cpp | 40 ++++++ src/SketchAPI/SketchAPI_BSpline.h | 6 + src/SketchPlugin/CMakeLists.txt | 1 + src/SketchPlugin/SketchPlugin_BSplineBase.cpp | 93 +++++--------- .../SketchPlugin_MacroBSpline.cpp | 30 +++-- src/SketchPlugin/SketchPlugin_MacroBSpline.h | 5 + src/SketchPlugin/Test/TestBSplineAddPole.py | 116 ++++++++++++++++++ .../PlaneGCSSolver_AttributeBuilder.cpp | 13 +- 8 files changed, 232 insertions(+), 72 deletions(-) create mode 100644 src/SketchPlugin/Test/TestBSplineAddPole.py diff --git a/src/SketchAPI/SketchAPI_BSpline.cpp b/src/SketchAPI/SketchAPI_BSpline.cpp index e804d164b..c89abac78 100644 --- a/src/SketchAPI/SketchAPI_BSpline.cpp +++ b/src/SketchAPI/SketchAPI_BSpline.cpp @@ -473,6 +473,46 @@ void SketchAPI_BSpline::dumpControlPolygon( theDumper << ")" << std::endl; } +static void setCoordinates(const FeaturePtr& theFeature, + const std::string& theAttrName, + const GeomPnt2dPtr& theCoordinates) +{ + AttributePoint2DPtr aPoint = + std::dynamic_pointer_cast(theFeature->attribute(theAttrName)); + aPoint->setValue(theCoordinates); +} + +bool SketchAPI_BSpline::insertPole(const int theIndex, + const GeomPnt2dPtr& theCoordinates, + const ModelHighAPI_Double& theWeight) +{ + std::ostringstream anActionName; + anActionName << SketchPlugin_BSplineBase::ADD_POLE_ACTION_ID() << "#" << theIndex; + bool isOk = feature()->customAction(anActionName.str()); + if (isOk) { + int anIndex = theIndex + 1; + if (feature()->getKind() == SketchPlugin_BSpline::ID() && anIndex + 1 >= poles()->size()) + anIndex = poles()->size() - 2; + // initialize coordinates and weight of new pole + poles()->setPnt(anIndex, theCoordinates); + weights()->setValue(anIndex, theWeight.value()); + + // update coordinates of points of control polygon + std::map aPoints, aLines; + collectAuxiliaryFeatures(feature(), aPoints, aLines); + std::map::iterator aFound = aPoints.find(anIndex); + if (aFound != aPoints.end()) + setCoordinates(aFound->second, SketchPlugin_Point::COORD_ID(), theCoordinates); + aFound = aLines.find(anIndex); + if (aFound != aLines.end()) + setCoordinates(aFound->second, SketchPlugin_Line::START_ID(), theCoordinates); + aFound = aLines.find(anIndex - 1); + if (aFound != aLines.end()) + setCoordinates(aFound->second, SketchPlugin_Line::END_ID(), theCoordinates); + } + return isOk; +} + // ================================================================================================= diff --git a/src/SketchAPI/SketchAPI_BSpline.h b/src/SketchAPI/SketchAPI_BSpline.h index 377a7b65d..056462523 100644 --- a/src/SketchAPI/SketchAPI_BSpline.h +++ b/src/SketchAPI/SketchAPI_BSpline.h @@ -97,6 +97,12 @@ public: const std::list& regular = std::list(), const std::list& auxiliary = std::list()) const; + /// Insert new pole after the pole with the given index + SKETCHAPI_EXPORT + bool insertPole(const int theIndex, + const std::shared_ptr& theCoordinates, + const ModelHighAPI_Double& theWeight = ModelHighAPI_Double(1.0)); + /// Dump wrapped feature SKETCHAPI_EXPORT virtual void dump(ModelHighAPI_Dumper& theDumper) const; diff --git a/src/SketchPlugin/CMakeLists.txt b/src/SketchPlugin/CMakeLists.txt index fbd8bb857..e68cdd0c4 100644 --- a/src/SketchPlugin/CMakeLists.txt +++ b/src/SketchPlugin/CMakeLists.txt @@ -221,6 +221,7 @@ ADD_UNIT_TESTS( Test3087_1.py Test3087_2.py TestArcBehavior.py + TestBSplineAddPole.py TestChangeSketchPlane1.py TestChangeSketchPlane2.py TestChangeSketchPlane3.py diff --git a/src/SketchPlugin/SketchPlugin_BSplineBase.cpp b/src/SketchPlugin/SketchPlugin_BSplineBase.cpp index 380887a64..a663675c1 100644 --- a/src/SketchPlugin/SketchPlugin_BSplineBase.cpp +++ b/src/SketchPlugin/SketchPlugin_BSplineBase.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -108,55 +109,6 @@ bool SketchPlugin_BSplineBase::isFixed() { } void SketchPlugin_BSplineBase::attributeChanged(const std::string& theID) { - // the second condition for unability to move external segments anywhere - if (theID == EXTERNAL_ID() || isFixed()) { - std::shared_ptr aSelection = data()->selection(EXTERNAL_ID())->value(); - if (!aSelection) { - // empty shape in selection shows that the shape is equal to context - ResultPtr anExtRes = selection(EXTERNAL_ID())->context(); - if (anExtRes) - aSelection = anExtRes->shape(); - } -//// // update arguments due to the selection value -//// if (aSelection && !aSelection->isNull() && aSelection->isEdge()) { -//// std::shared_ptr anEdge(new GeomAPI_Edge(aSelection)); -//// std::shared_ptr anEllipse = anEdge->ellipse(); -//// -//// bool aWasBlocked = data()->blockSendAttributeUpdated(true); -//// std::shared_ptr aCenterAttr = -//// std::dynamic_pointer_cast(attribute(CENTER_ID())); -//// aCenterAttr->setValue(sketch()->to2D(anEllipse->center())); -//// -//// std::shared_ptr aFocusAttr = -//// std::dynamic_pointer_cast(attribute(FIRST_FOCUS_ID())); -//// aFocusAttr->setValue(sketch()->to2D(anEllipse->firstFocus())); -//// -//// std::shared_ptr aStartAttr = -//// std::dynamic_pointer_cast(attribute(START_POINT_ID())); -//// aStartAttr->setValue(sketch()->to2D(anEdge->firstPoint())); -//// -//// std::shared_ptr aEndAttr = -//// std::dynamic_pointer_cast(attribute(END_POINT_ID())); -//// aEndAttr->setValue(sketch()->to2D(anEdge->lastPoint())); -//// -//// real(MAJOR_RADIUS_ID())->setValue(anEllipse->majorRadius()); -//// real(MINOR_RADIUS_ID())->setValue(anEllipse->minorRadius()); -//// -//// double aStartParam, aMidParam, aEndParam; -//// anEllipse->parameter(anEdge->firstPoint(), tolerance, aStartParam); -//// anEllipse->parameter(anEdge->middlePoint(), tolerance, aMidParam); -//// anEllipse->parameter(anEdge->lastPoint(), tolerance, aEndParam); -//// if (aEndParam < aStartParam) -//// aEndParam += 2.0 * PI; -//// if (aMidParam < aStartParam) -//// aMidParam += 2.0 * PI; -//// boolean(REVERSED_ID())->setValue(aMidParam > aEndParam); -//// -//// data()->blockSendAttributeUpdated(aWasBlocked, false); -//// -//// fillCharacteristicPoints(); -//// } - } } bool SketchPlugin_BSplineBase::customAction(const std::string& theActionId) @@ -175,7 +127,7 @@ bool SketchPlugin_BSplineBase::customAction(const std::string& theActionId) } std::string aMsg = "Error: Feature \"%1\" does not support action \"%2\"."; - Events_InfoMessage("SketchPlugin_BSplineBase", aMsg).arg(getKind()).arg(anAction).send(); + Events_InfoMessage("SketchPlugin_BSplineBase", aMsg).arg(getKind()).arg(theActionId).send(); return false; } @@ -189,31 +141,41 @@ bool SketchPlugin_BSplineBase::addPole(const int theAfter) // find internal coincidences applied to the poles with greater indices std::list aCoincidentPoleIndex; + std::map aControlPoles, aControlSegments; bool hasAuxSegment = false; const std::set& aRefs = data()->refsToMe(); for (std::set::iterator anIt = aRefs.begin(); anIt != aRefs.end(); ++anIt) { FeaturePtr aFeature = ModelAPI_Feature::feature((*anIt)->owner()); if (aFeature->getKind() == SketchPlugin_ConstraintCoincidenceInternal::ID()) { AttributeIntegerPtr anIndex; - if ((*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_A()) + AttributeRefAttrPtr aNonSplinePoint; + if ((*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_A()) { anIndex = aFeature->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_A()); - else if ((*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_B()) + aNonSplinePoint = aFeature->refattr(SketchPlugin_Constraint::ENTITY_B()); + } + else if ((*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_B()) { anIndex = aFeature->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B()); + aNonSplinePoint = aFeature->refattr(SketchPlugin_Constraint::ENTITY_A()); + } if (anIndex && anIndex->isInitialized()) { - if (anIndex->value() > anAfter) + if (anIndex->value() > anAfter) { aCoincidentPoleIndex.push_back(anIndex); + FeaturePtr aParent = ModelAPI_Feature::feature(aNonSplinePoint->attr()->owner()); + if (aParent->getKind() == SketchPlugin_Point::ID()) + aControlPoles[anIndex->value()] = aParent; + else if (aParent->getKind() == SketchPlugin_Line::ID() && + aNonSplinePoint->attr()->id() == SketchPlugin_Line::START_ID()) + aControlSegments[anIndex->value()] = aParent; + } else if (anIndex->value() == anAfter && !hasAuxSegment) { // check the constrained object is a segment of the control polygon - const std::string& anOtherAttr = - (*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_A() ? - SketchPlugin_ConstraintCoincidenceInternal::ENTITY_B() : - SketchPlugin_ConstraintCoincidenceInternal::ENTITY_A(); - AttributeRefAttrPtr aRefAttr = aFeature->refattr(anOtherAttr); - if (aRefAttr && !aRefAttr->isObject() && - aRefAttr->attr()->id() == SketchPlugin_Line::START_ID()) { + if (aNonSplinePoint && !aNonSplinePoint->isObject() && + aNonSplinePoint->attr()->id() == SketchPlugin_Line::START_ID()) { hasAuxSegment = true; aCoincidentPoleIndex.push_back(anIndex); + aControlSegments[anIndex->value()] = + ModelAPI_Feature::feature(aNonSplinePoint->attr()->owner()); } } } @@ -283,5 +245,16 @@ bool SketchPlugin_BSplineBase::addPole(const int theAfter) if (hasAuxSegment) SketchPlugin_MacroBSpline::createAuxiliarySegment(aPolesArray, anAfter, anAfter + 1); + // update names of features representing control polygon + for (std::map::iterator anIt = aControlPoles.begin(); + anIt != aControlPoles.end(); ++anIt) { + SketchPlugin_MacroBSpline::assignDefaultNameForAux(anIt->second, aPolesArray, anIt->first + 1); + } + for (std::map::iterator anIt = aControlSegments.begin(); + anIt != aControlSegments.end(); ++anIt) { + SketchPlugin_MacroBSpline::assignDefaultNameForAux(anIt->second, aPolesArray, + anIt->first + 1, (anIt->first + 2) % aPolesArray->size()); + } + return true; } diff --git a/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp b/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp index 0ed1258b5..100e011e6 100644 --- a/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp +++ b/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp @@ -309,6 +309,24 @@ AISObjectPtr SketchPlugin_MacroBSpline::getAISObject(AISObjectPtr thePrevious) // ========================== Auxiliary functions =========================================== +void SketchPlugin_MacroBSpline::assignDefaultNameForAux(FeaturePtr theAuxFeature, + AttributePoint2DArrayPtr theBSplinePoles, + const int thePoleIndex1, + const int thePoleIndex2) +{ + FeaturePtr aBSpline = ModelAPI_Feature::feature(theBSplinePoles->owner()); + + std::ostringstream aName; + aName << aBSpline->name(); + if (theAuxFeature->getKind() == SketchPlugin_Point::ID()) + aName << "_" << theBSplinePoles->id() << "_" << thePoleIndex1; + else + aName << "_segment_" << thePoleIndex1 << "_" << thePoleIndex2; + + theAuxFeature->data()->setName(aName.str()); + theAuxFeature->lastResult()->data()->setName(aName.str()); +} + FeaturePtr SketchPlugin_MacroBSpline::createAuxiliaryPole(AttributePoint2DArrayPtr theBSplinePoles, const int thePoleIndex) { @@ -329,11 +347,7 @@ FeaturePtr SketchPlugin_MacroBSpline::createAuxiliaryPole(AttributePoint2DArrayP aCoord->setValue(aPole); aPointFeature->execute(); - - std::ostringstream aName; - aName << aBSpline->name() << "_" << theBSplinePoles->id() << "_" << thePoleIndex; - aPointFeature->data()->setName(aName.str()); - aPointFeature->lastResult()->data()->setName(aName.str()); + assignDefaultNameForAux(aPointFeature, theBSplinePoles, thePoleIndex); // internal constraint to keep position of the point createInternalConstraint(aSketch, aCoord, theBSplinePoles, thePoleIndex); @@ -364,11 +378,7 @@ void SketchPlugin_MacroBSpline::createAuxiliarySegment(AttributePoint2DArrayPtr aLineEnd->setValue(theBSplinePoles->pnt(thePoleIndex2)); aLineFeature->execute(); - - std::ostringstream aName; - aName << aBSpline->name() << "_segment_" << thePoleIndex1 << "_" << thePoleIndex2; - aLineFeature->data()->setName(aName.str()); - aLineFeature->lastResult()->data()->setName(aName.str()); + assignDefaultNameForAux(aLineFeature, theBSplinePoles, thePoleIndex1, thePoleIndex2); // internal constraints to keep the segment position createInternalConstraint(aSketch, aLineStart, theBSplinePoles, thePoleIndex1); diff --git a/src/SketchPlugin/SketchPlugin_MacroBSpline.h b/src/SketchPlugin/SketchPlugin_MacroBSpline.h index 860c34f9d..d33332553 100644 --- a/src/SketchPlugin/SketchPlugin_MacroBSpline.h +++ b/src/SketchPlugin/SketchPlugin_MacroBSpline.h @@ -123,6 +123,11 @@ private: static void createAuxiliarySegment(std::shared_ptr theBSplinePoles, const int thePoleIndex1, const int thePoleIndex2); + /// Set name of auxiliary feature representing the control polygon + static void assignDefaultNameForAux(FeaturePtr theAuxFeature, + std::shared_ptr theBSplinePoles, + const int thePoleIndex1, + const int thePoleIndex2 = -1); friend class SketchPlugin_BSplineBase; private: diff --git a/src/SketchPlugin/Test/TestBSplineAddPole.py b/src/SketchPlugin/Test/TestBSplineAddPole.py new file mode 100644 index 000000000..09a577c35 --- /dev/null +++ b/src/SketchPlugin/Test/TestBSplineAddPole.py @@ -0,0 +1,116 @@ +# 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 +# + +""" + Test for adding a pole to already created B-spline curve +""" + +from salome.shaper import model +from GeomAPI import * +import random + +TOLERANCE = 1.e-7 + +def assertSubFeatures(theSketch, theNbPoints, theNbLines, theNbSplines, theNbSplinesP, theNbCoincidences, theNbInternal): + model.testNbSubFeatures(theSketch, "SketchPoint", theNbPoints) + model.testNbSubFeatures(theSketch, "SketchLine", theNbLines) + model.testNbSubFeatures(theSketch, "SketchBSpline", theNbSplines) + model.testNbSubFeatures(theSketch, "SketchBSplinePeriodic", theNbSplinesP) + model.testNbSubFeatures(theSketch, "SketchConstraintCoincidence", theNbCoincidences) + model.testNbSubFeatures(theSketch, "SketchConstraintCoincidenceInternal", theNbInternal) + +def assertPoles(thePoles, theReference): + assert(thePoles.size() == len(theReference)) + for ind in range(0, len(theReference)): + pole = thePoles.pnt(ind) + ref = GeomAPI_Pnt2d(theReference[ind][0], theReference[ind][1]) + assert(model.distancePointPoint(pole, ref) < TOLERANCE), "Index = {}, pole = ({}, {}), refdata = ({}, {})".format(ind, pole.x(), pole.y(), ref.x(), ref.y()) + + +model.begin() +partSet = model.moduleDocument() +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() +Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY")) + +SketchBSpline_1_poles = [(-25, 5), (-15, 35), (15, 35), (28, 5)] +SketchBSpline_1 = Sketch_1.addSpline(poles = SketchBSpline_1_poles) +[SketchPoint_1, SketchPoint_2, SketchPoint_3, SketchPoint_4] = SketchBSpline_1.controlPoles(auxiliary = [0, 1, 2, 3]) +[SketchLine_1, SketchLine_2, SketchLine_3] = SketchBSpline_1.controlPolygon(auxiliary = [0, 1, 2]) + +SketchBSplinePeriodic_1_poles = [(-20, -10), (20, -40), (20, -10), (-20, -40)] +SketchBSplinePeriodic_1 = Sketch_1.addSpline(poles = SketchBSplinePeriodic_1_poles, periodic = True) +[SketchPoint_5, SketchPoint_6, SketchPoint_7, SketchPoint_8] = SketchBSplinePeriodic_1.controlPoles(auxiliary = [0, 1, 2, 3]) +[SketchLine_4, SketchLine_5, SketchLine_6, SketchLine_7] = SketchBSplinePeriodic_1.controlPolygon(auxiliary = [0, 1, 2, 3]) +model.do() + +# check original values +NBPOINTS = 8 +NBLINES = 7 +NBSPLINES = 1 +NBSPLINESPERIODIC = 1 +NBCOINCIDENCES = 0 +NBINTERNAL = 22 +assertSubFeatures(Sketch_1, NBPOINTS, NBLINES, NBSPLINES, NBSPLINESPERIODIC, NBCOINCIDENCES, NBINTERNAL) +assertPoles(SketchBSpline_1.poles(), SketchBSpline_1_poles) +assertPoles(SketchBSplinePeriodic_1.poles(), SketchBSplinePeriodic_1_poles) + +# add poles to non-periodic B-spline +ind = 0 +while ind < len(SketchBSpline_1_poles): + x = random.uniform(-25, 25) + y = random.uniform(5, 40) + SketchBSpline_1.insertPole(ind, GeomAPI_Pnt2d(x, y)) + if ind + 1 < len(SketchBSpline_1_poles): + SketchBSpline_1_poles.insert(ind + 1, (x, y)) + else: + SketchBSpline_1_poles.insert(len(SketchBSpline_1_poles) - 1, (x, y)) + ind += 2 + model.do() + + NBPOINTS += 1 + NBLINES += 1 + NBINTERNAL += 3 + assertSubFeatures(Sketch_1, NBPOINTS, NBLINES, NBSPLINES, NBSPLINESPERIODIC, NBCOINCIDENCES, NBINTERNAL) + assertPoles(SketchBSpline_1.poles(), SketchBSpline_1_poles) + +# add poles to periodic B-spline +ind = 0 +while ind < len(SketchBSplinePeriodic_1_poles): + x = random.uniform(-25, 25) + y = random.uniform(-45, -5) + SketchBSplinePeriodic_1.insertPole(ind, GeomAPI_Pnt2d(x, y)) + SketchBSplinePeriodic_1_poles.insert(ind + 1, (x, y)) + ind += 2 + model.do() + + NBPOINTS += 1 + NBLINES += 1 + NBINTERNAL += 3 + assertSubFeatures(Sketch_1, NBPOINTS, NBLINES, NBSPLINES, NBSPLINESPERIODIC, NBCOINCIDENCES, NBINTERNAL) + assertPoles(SketchBSplinePeriodic_1.poles(), SketchBSplinePeriodic_1_poles) + +model.end() + +# check error on incorrect action +model.begin() +assert(not SketchBSpline_1.feature().customAction("wrong_action")) +model.end() + +#assert(model.checkPythonDump()) diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp index a446abc66..439a18f17 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp @@ -216,9 +216,9 @@ bool PlaneGCSSolver_AttributeBuilder::updateAttribute( std::shared_ptr anAttribute = std::dynamic_pointer_cast(theAttribute); + std::vector aPointsArray = aWrapper->array(); + std::vector::iterator aPos = aPointsArray.begin(); if (aWrapper->size() != anAttribute->size()) { - std::vector aPointsArray = aWrapper->array(); - std::vector::iterator aPos = aPointsArray.begin(); while (anAttribute->size() > (int)aPointsArray.size()) { // add points to the middle of array GeomPnt2dPtr aValue; @@ -242,6 +242,15 @@ bool PlaneGCSSolver_AttributeBuilder::updateAttribute( aWrapper->setArray(aPointsArray); } + else { + // update coordinates of points + for (int anIndex = 0; aPos != aPointsArray.end(); ++aPos, ++anIndex) { + const GCSPointPtr& aGCSPoint = (*aPos)->point(); + GeomPnt2dPtr aCoord = anAttribute->pnt(anIndex); + *aGCSPoint->x = aCoord->x(); + *aGCSPoint->y = aCoord->y(); + } + } } else if (theEntity->type() == ENTITY_SCALAR_ARRAY) { std::shared_ptr aWrapper = -- 2.39.2