From: azv Date: Sat, 25 Mar 2017 09:35:35 +0000 (+0300) Subject: Issue #2024: Redesign of circle and arc of circle X-Git-Tag: V_2.7.0~183 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=d58fe13200f3fc72c246e2d4b9670a4a8460de4e;p=modules%2Fshaper.git Issue #2024: Redesign of circle and arc of circle * Prepare shape for MacroCircle according to already selected points. * Remove waste fields (myCenter and myRadius) from MacroCircle class. * Adjust test cases for creating circle. --- diff --git a/src/GeomAPI/GeomAPI_Circ2d.cpp b/src/GeomAPI/GeomAPI_Circ2d.cpp index f4533c144..743b44db2 100644 --- a/src/GeomAPI/GeomAPI_Circ2d.cpp +++ b/src/GeomAPI/GeomAPI_Circ2d.cpp @@ -231,7 +231,7 @@ private: GccEnt::Unqualified(aCurve1->Line()), aPoint, Precision::Confusion())); } - } else if (aCurve2->GetType() == GeomAbs_Circle) { + } else if (aCurve1->GetType() == GeomAbs_Circle) { if (aCurve2->GetType() == GeomAbs_Line) { aCircleBuilder = std::shared_ptr( new GccAna_Circ2d3Tan(GccEnt::Unqualified(aCurve1->Circle()), diff --git a/src/ModelAPI/ModelAPI_Attribute.cpp b/src/ModelAPI/ModelAPI_Attribute.cpp index 69465bf61..bdf4a4df9 100644 --- a/src/ModelAPI/ModelAPI_Attribute.cpp +++ b/src/ModelAPI/ModelAPI_Attribute.cpp @@ -86,4 +86,7 @@ void ModelAPI_Attribute::setID(const std::string theID) void ModelAPI_Attribute::reinit() {} -void ModelAPI_Attribute::reset() {} +void ModelAPI_Attribute::reset() +{ + myIsInitialized = false; +} diff --git a/src/SketchPlugin/SketchPlugin_MacroCircle.cpp b/src/SketchPlugin/SketchPlugin_MacroCircle.cpp index 2e67b322a..d1bbbd65a 100644 --- a/src/SketchPlugin/SketchPlugin_MacroCircle.cpp +++ b/src/SketchPlugin/SketchPlugin_MacroCircle.cpp @@ -21,9 +21,7 @@ #include #include -#include #include -#include #include #include @@ -48,8 +46,7 @@ namespace { SketchPlugin_MacroCircle::SketchPlugin_MacroCircle() -: SketchPlugin_SketchEntity(), -myRadius(0) +: SketchPlugin_SketchEntity() { } @@ -86,9 +83,6 @@ void SketchPlugin_MacroCircle::execute() createCircleByCenterAndPassed(); else if (aType == CIRCLE_TYPE_BY_THREE_POINTS()) createCircleByThreePoints(); - - myCenter.reset(); - myRadius = 0; } static void convertToPointOrTangent(const AttributeRefAttrPtr& theRefAttr, @@ -114,24 +108,8 @@ static void convertToPointOrTangent(const AttributeRefAttrPtr& theRefAttr, void SketchPlugin_MacroCircle::createCircleByCenterAndPassed() { - AttributePtr aPassedAttr = attribute(PASSED_POINT_ID()); - AttributeRefAttrPtr aPassedRef = refattr(PASSED_POINT_REF_ID()); - // Calculate circle parameters - std::shared_ptr aCenter = - std::dynamic_pointer_cast(attribute(CENTER_POINT_ID()))->pnt(); - std::shared_ptr aPassedPoint; - std::shared_ptr aTangentCurve; - convertToPointOrTangent(aPassedRef, aPassedAttr, aPassedPoint, aTangentCurve); - - // Build a circle - std::shared_ptr aCircle; - if (aTangentCurve) { - std::shared_ptr anAxis = SketchPlugin_Sketch::plane(sketch()); - aCircle = std::shared_ptr(new GeomAPI_Circ2d(aCenter, aTangentCurve, anAxis)); - } else - aCircle = std::shared_ptr(new GeomAPI_Circ2d(aCenter, aPassedPoint)); - // Create circle feature. + std::shared_ptr aCircle = shapeByCenterAndPassed(); FeaturePtr aCircleFeature = createCircleFeature(aCircle); // Create constraints. @@ -149,32 +127,12 @@ void SketchPlugin_MacroCircle::createCircleByCenterAndPassed() void SketchPlugin_MacroCircle::createCircleByThreePoints() { - std::string aPointAttr[3] = { FIRST_POINT_ID(), - SECOND_POINT_ID(), - THIRD_POINT_ID() }; std::string aPointRef[3] = { FIRST_POINT_REF_ID(), SECOND_POINT_REF_ID(), THIRD_POINT_REF_ID() }; - std::shared_ptr aPassedEntities[3]; - for (int i = 0; i < 3; ++i) { - AttributePtr aPassedAttr = attribute(aPointAttr[i]); - AttributeRefAttrPtr aPassedRef = refattr(aPointRef[i]); - // calculate circle parameters - std::shared_ptr aPassedPoint; - std::shared_ptr aTangentCurve; - convertToPointOrTangent(aPassedRef, aPassedAttr, aPassedPoint, aTangentCurve); - - if (aPassedPoint) - aPassedEntities[i] = aPassedPoint; - else - aPassedEntities[i] = aTangentCurve; - } - - std::shared_ptr anAxis = SketchPlugin_Sketch::plane(sketch()); - std::shared_ptr aCircle = std::shared_ptr( - new GeomAPI_Circ2d(aPassedEntities[0], aPassedEntities[1], aPassedEntities[2], anAxis)); // Create circle feature. + std::shared_ptr aCircle = shapeByThreePoints(); FeaturePtr aCircleFeature = createCircleFeature(aCircle); ResultPtr aCircleResult = aCircleFeature->lastResult(); @@ -198,24 +156,147 @@ FeaturePtr SketchPlugin_MacroCircle::createCircleFeature( return aCircleFeature; } -AISObjectPtr SketchPlugin_MacroCircle::getAISObject(AISObjectPtr thePrevious) +std::shared_ptr SketchPlugin_MacroCircle::shapeByCenterAndPassed() { - if(!myCenter.get() || myRadius < tolerance) { - return AISObjectPtr(); + AttributePtr aCenterAttr = attribute(CENTER_POINT_ID()); + AttributePtr aPassedAttr = attribute(PASSED_POINT_ID()); + if (!aCenterAttr->isInitialized() || !aPassedAttr->isInitialized()) + return std::shared_ptr(); + + AttributeRefAttrPtr aPassedRef = refattr(PASSED_POINT_REF_ID()); + // Calculate circle parameters + std::shared_ptr aCenter = + std::dynamic_pointer_cast(aCenterAttr)->pnt(); + std::shared_ptr aPassedPoint; + std::shared_ptr aTangentCurve; + convertToPointOrTangent(aPassedRef, aPassedAttr, aPassedPoint, aTangentCurve); + + // Build a circle + std::shared_ptr aCircle; + if (aTangentCurve) { + std::shared_ptr anAxis = SketchPlugin_Sketch::plane(sketch()); + aCircle = std::shared_ptr(new GeomAPI_Circ2d(aCenter, aTangentCurve, anAxis)); + } else + aCircle = std::shared_ptr(new GeomAPI_Circ2d(aCenter, aPassedPoint)); + return aCircle; +} + +std::shared_ptr SketchPlugin_MacroCircle::shapeByThreePoints() +{ + std::string aPointAttr[3] = { FIRST_POINT_ID(), + SECOND_POINT_ID(), + THIRD_POINT_ID() }; + std::string aPointRef[3] = { FIRST_POINT_REF_ID(), + SECOND_POINT_REF_ID(), + THIRD_POINT_REF_ID() }; + std::shared_ptr aPassedEntities[3]; + for (int aPntIndex = 0; aPntIndex < 3; ++aPntIndex) { + AttributePtr aPassedAttr = attribute(aPointAttr[aPntIndex]); + if (!aPassedAttr->isInitialized()) + break; + + AttributeRefAttrPtr aPassedRef = refattr(aPointRef[aPntIndex]); + // calculate circle parameters + std::shared_ptr aPassedPoint; + std::shared_ptr aTangentCurve; + convertToPointOrTangent(aPassedRef, aPassedAttr, aPassedPoint, aTangentCurve); + + if (aPassedPoint) + aPassedEntities[aPntIndex] = aPassedPoint; + else + aPassedEntities[aPntIndex] = aTangentCurve; } + std::shared_ptr anAxis = SketchPlugin_Sketch::plane(sketch()); + std::shared_ptr aCircle = std::shared_ptr( + new GeomAPI_Circ2d(aPassedEntities[0], aPassedEntities[1], aPassedEntities[2], anAxis)); + if (!aCircle->implPtr()) + return std::shared_ptr(); + return aCircle; +} + +std::shared_ptr SketchPlugin_MacroCircle::shapeByTwoPassedPoints() +{ + std::string aPointAttr[2] = { FIRST_POINT_ID(), + SECOND_POINT_ID() }; + std::string aPointRef[2] = { FIRST_POINT_REF_ID(), + SECOND_POINT_REF_ID() }; + std::shared_ptr aPassedPoints[2]; // there is possible only two passed points + std::shared_ptr aPassedEntities[3]; + int aPntIndex = 0; + for (; aPntIndex < 2; ++aPntIndex) { + AttributePtr aPassedAttr = attribute(aPointAttr[aPntIndex]); + if (!aPassedAttr->isInitialized()) + break; + + AttributeRefAttrPtr aPassedRef = refattr(aPointRef[aPntIndex]); + // calculate circle parameters + std::shared_ptr aPassedPoint; + std::shared_ptr aTangentCurve; + convertToPointOrTangent(aPassedRef, aPassedAttr, aPassedPoint, aTangentCurve); + + if (aPassedPoint) { + aPassedEntities[aPntIndex] = aPassedPoint; + aPassedPoints[aPntIndex] = aPassedPoint; + } else { + aPassedEntities[aPntIndex] = aTangentCurve; + // if the circle is tangent to any curve, + // the third point will be initialized by the tangent point + aPassedEntities[2] = std::dynamic_pointer_cast(aPassedAttr)->pnt(); + } + } + if (aPntIndex <= 1) + return std::shared_ptr(); + + std::shared_ptr aCircle; + if (aPassedEntities[2]) { + std::shared_ptr anAxis = SketchPlugin_Sketch::plane(sketch()); + aCircle = std::shared_ptr( + new GeomAPI_Circ2d(aPassedEntities[0], aPassedEntities[1], aPassedEntities[2], anAxis)); + } else { + // the circle is defined by two points, calculate its parameters manually + std::shared_ptr aCenter(new GeomAPI_Pnt2d( + (aPassedPoints[0]->x() + aPassedPoints[1]->x()) * 0.5, + (aPassedPoints[0]->y() + aPassedPoints[1]->y()) * 0.5)); + aCircle = std::shared_ptr(new GeomAPI_Circ2d(aCenter, aPassedPoints[0])); + } + if (!aCircle->implPtr()) + return std::shared_ptr(); + return aCircle; +} + +AISObjectPtr SketchPlugin_MacroCircle::getAISObject(AISObjectPtr thePrevious) +{ SketchPlugin_Sketch* aSketch = sketch(); if(!aSketch) { return AISObjectPtr(); } + // Create circle on the sketch plane + std::shared_ptr aCircleOnSketch; + std::string aType = string(CIRCLE_TYPE())->value(); + if (aType == CIRCLE_TYPE_BY_CENTER_AND_PASSED_POINTS()) + aCircleOnSketch = shapeByCenterAndPassed(); + else if (aType == CIRCLE_TYPE_BY_THREE_POINTS()) { + if (attribute(THIRD_POINT_ID())->isInitialized()) + aCircleOnSketch = shapeByThreePoints(); + else + aCircleOnSketch = shapeByTwoPassedPoints(); + } + + if (!aCircleOnSketch) + return AISObjectPtr(); + + std::shared_ptr aCenter2D = aCircleOnSketch->center(); + double aRadius = aCircleOnSketch->radius(); + // Compute a circle in 3D view. - std::shared_ptr aCenter(aSketch->to3D(myCenter->x(), myCenter->y())); + std::shared_ptr aCenter(aSketch->to3D(aCenter2D->x(), aCenter2D->y())); std::shared_ptr aNDir = std::dynamic_pointer_cast( aSketch->data()->attribute(SketchPlugin_Sketch::NORM_ID())); std::shared_ptr aNormal = aNDir->dir(); - GeomShapePtr aCircleShape = GeomAlgoAPI_EdgeBuilder::lineCircle(aCenter, aNormal, myRadius); + GeomShapePtr aCircleShape = GeomAlgoAPI_EdgeBuilder::lineCircle(aCenter, aNormal, aRadius); GeomShapePtr aCenterPointShape = GeomAlgoAPI_PointBuilder::vertex(aCenter); if(!aCircleShape.get() || !aCenterPointShape.get()) { return AISObjectPtr(); @@ -235,24 +316,19 @@ AISObjectPtr SketchPlugin_MacroCircle::getAISObject(AISObjectPtr thePrevious) } void SketchPlugin_MacroCircle::attributeChanged(const std::string& theID) { - // If circle type switched reset according attributes. + double aRadius = 0.0; + // If circle type switched reset all attributes. if(theID == CIRCLE_TYPE()) { - std::string aType = string(CIRCLE_TYPE())->value(); - if(aType == CIRCLE_TYPE_BY_CENTER_AND_PASSED_POINTS()) { - SketchPlugin_Tools::resetAttribute(this, CENTER_POINT_ID()); - SketchPlugin_Tools::resetAttribute(this, CENTER_POINT_REF_ID()); - SketchPlugin_Tools::resetAttribute(this, PASSED_POINT_ID()); - SketchPlugin_Tools::resetAttribute(this, PASSED_POINT_REF_ID()); - } else if(aType == CIRCLE_TYPE_BY_THREE_POINTS()) { - SketchPlugin_Tools::resetAttribute(this, FIRST_POINT_ID()); - SketchPlugin_Tools::resetAttribute(this, FIRST_POINT_REF_ID()); - SketchPlugin_Tools::resetAttribute(this, SECOND_POINT_ID()); - SketchPlugin_Tools::resetAttribute(this, SECOND_POINT_REF_ID()); - SketchPlugin_Tools::resetAttribute(this, THIRD_POINT_ID()); - SketchPlugin_Tools::resetAttribute(this, THIRD_POINT_REF_ID()); - } - myCenter.reset(); - myRadius = 0; + SketchPlugin_Tools::resetAttribute(this, CENTER_POINT_ID()); + SketchPlugin_Tools::resetAttribute(this, CENTER_POINT_REF_ID()); + SketchPlugin_Tools::resetAttribute(this, PASSED_POINT_ID()); + SketchPlugin_Tools::resetAttribute(this, PASSED_POINT_REF_ID()); + SketchPlugin_Tools::resetAttribute(this, FIRST_POINT_ID()); + SketchPlugin_Tools::resetAttribute(this, FIRST_POINT_REF_ID()); + SketchPlugin_Tools::resetAttribute(this, SECOND_POINT_ID()); + SketchPlugin_Tools::resetAttribute(this, SECOND_POINT_REF_ID()); + SketchPlugin_Tools::resetAttribute(this, THIRD_POINT_ID()); + SketchPlugin_Tools::resetAttribute(this, THIRD_POINT_REF_ID()); } else if(theID == CENTER_POINT_ID() || theID == PASSED_POINT_ID()) { std::shared_ptr aCenterPointAttr = std::dynamic_pointer_cast(attribute(CENTER_POINT_ID())); @@ -265,8 +341,7 @@ void SketchPlugin_MacroCircle::attributeChanged(const std::string& theID) { return; } - myCenter = aCenterPointAttr->pnt(); - myRadius = myCenter->distance(aPassedPointAttr->pnt()); + aRadius = aCenterPointAttr->pnt()->distance(aPassedPointAttr->pnt()); } else if(theID == FIRST_POINT_ID() || theID == SECOND_POINT_ID() || theID == THIRD_POINT_ID()) { std::shared_ptr aPoints[3]; int aNbInitialized = 0; @@ -277,25 +352,19 @@ void SketchPlugin_MacroCircle::attributeChanged(const std::string& theID) { aPoints[aNbInitialized++] = aCurPnt->pnt(); } - if(aNbInitialized == 1) { + std::shared_ptr aCircle; + if(aNbInitialized == 1) return; - } else if(aNbInitialized == 2) { - std::shared_ptr aCenterXY = - aPoints[0]->xy()->added(aPoints[1]->xy())->multiplied(0.5); - myCenter.reset(new GeomAPI_Pnt2d(aCenterXY->x(), aCenterXY->y())); - myRadius = aPoints[0]->distance(aPoints[1]) * 0.5; - } else { - std::shared_ptr aCircle( - new GeomAPI_Circ2d(aPoints[0], aPoints[1], aPoints[2])); - myCenter = aCircle->center(); - if(myCenter.get()) { - myRadius = aCircle->radius(); - } - } + else if(aNbInitialized == 2) + aCircle = shapeByTwoPassedPoints(); + else + aCircle = shapeByThreePoints(); + if (aCircle) + aRadius = aCircle->radius(); } AttributeDoublePtr aRadiusAttr = real(CIRCLE_RADIUS_ID()); bool aWasBlocked = data()->blockSendAttributeUpdated(true); - aRadiusAttr->setValue(myRadius); + aRadiusAttr->setValue(aRadius); data()->blockSendAttributeUpdated(aWasBlocked, false); } diff --git a/src/SketchPlugin/SketchPlugin_MacroCircle.h b/src/SketchPlugin/SketchPlugin_MacroCircle.h index 974a0eeb6..27130a4e7 100644 --- a/src/SketchPlugin/SketchPlugin_MacroCircle.h +++ b/src/SketchPlugin/SketchPlugin_MacroCircle.h @@ -163,14 +163,15 @@ class SketchPlugin_MacroCircle: public SketchPlugin_SketchEntity, SketchPlugin_MacroCircle(); private: + std::shared_ptr shapeByCenterAndPassed(); + std::shared_ptr shapeByThreePoints(); + /// Creates shape if only two of three points is initialized + std::shared_ptr shapeByTwoPassedPoints(); + void createCircleByCenterAndPassed(); void createCircleByThreePoints(); FeaturePtr createCircleFeature(const std::shared_ptr& theCircle); - -private: - std::shared_ptr myCenter; - double myRadius; }; #endif diff --git a/src/SketchPlugin/Test/TestCreateCircleByCenterAndPassed.py b/src/SketchPlugin/Test/TestCreateCircleByCenterAndPassed.py index e6695c852..272399734 100644 --- a/src/SketchPlugin/Test/TestCreateCircleByCenterAndPassed.py +++ b/src/SketchPlugin/Test/TestCreateCircleByCenterAndPassed.py @@ -248,7 +248,7 @@ aPassedRef.setObject(aLine.lastResult()) aPassed.setValue(aLineStart[0], aLineStart[1]) aSession.finishOperation() aLastFeature = aSketchFeature.subFeature(aSketchFeature.numberOfSubs() - 1) -assert aLastFeature == aCircle, "ERROR: SketchMacroCircle has NOT expected to be valid" +assert aLastFeature.getKind() == "SketchMacroCircle", "ERROR: SketchMacroCircle has NOT expected to be valid" aDocument.removeFeature(aCircle) assert (aSketchFeature.numberOfSubs() == 10) diff --git a/src/SketchPlugin/Test/TestCreateCircleByThreePoints.py b/src/SketchPlugin/Test/TestCreateCircleByThreePoints.py index 22f70e6ed..060127a34 100644 --- a/src/SketchPlugin/Test/TestCreateCircleByThreePoints.py +++ b/src/SketchPlugin/Test/TestCreateCircleByThreePoints.py @@ -61,8 +61,8 @@ def verifyTangentCircles(theCircle1, theCircle2): def verifyTangentCircleArc(theCircle, theArc): aCircleCenter = geomDataAPI_Point2D(theCircle.attribute("circle_center")) - anArcCenter = geomDataAPI_Point2D(theArc.attribute("ArcCenter")) - anArcStart = geomDataAPI_Point2D(theArc.attribute("ArcStartPoint")) + anArcCenter = geomDataAPI_Point2D(theArc.attribute("center_point")) + anArcStart = geomDataAPI_Point2D(theArc.attribute("start_point")) aDistCC = distancePointPoint(aCircleCenter, anArcCenter) aCircleRadius = theCircle.real("circle_radius").value() anArcRadius = distancePointPoint(anArcCenter, anArcStart) @@ -200,11 +200,11 @@ anArcStart = [anArcCenter[0], anArcCenter[1] - anArcRadius] anArcEnd = [anArcCenter[0], anArcCenter[1] + anArcRadius] aSession.startOperation() anArc = aSketchFeature.addFeature("SketchArc") -anArcCenterPnt = geomDataAPI_Point2D(anArc.attribute("ArcCenter")) +anArcCenterPnt = geomDataAPI_Point2D(anArc.attribute("center_point")) anArcCenterPnt.setValue(anArcCenter[0], anArcCenter[1]) -anArcStartPnt = geomDataAPI_Point2D(anArc.attribute("ArcStartPoint")) +anArcStartPnt = geomDataAPI_Point2D(anArc.attribute("start_point")) anArcStartPnt.setValue(anArcStart[0], anArcStart[1]) -anArcEndPnt = geomDataAPI_Point2D(anArc.attribute("ArcEndPoint")) +anArcEndPnt = geomDataAPI_Point2D(anArc.attribute("end_point")) anArcEndPnt.setValue(anArcEnd[0], anArcEnd[1]) aSession.finishOperation() # create new circle @@ -267,7 +267,7 @@ aCirclePnt3Ref.setObject(aLine.lastResult()) aCirclePnt3.setValue(aLineEnd[0], aLineEnd[1]) aSession.finishOperation() aLastFeature = aSketchFeature.subFeature(aSketchFeature.numberOfSubs() - 1) -assert aLastFeature == aCircle, "ERROR: SketchMacroCircle has NOT expected to be valid" +assert aLastFeature.getKind() == "SketchMacroCircle", "ERROR: SketchMacroCircle has NOT expected to be valid" aDocument.removeFeature(aCircle) assert (aSketchFeature.numberOfSubs() == 12) @@ -294,7 +294,7 @@ aCirclePnt3Ref.setObject(aLine.lastResult()) aCirclePnt3.setValue(aLineEnd[0], aLineEnd[1]) aSession.finishOperation() aLastFeature = aSketchFeature.subFeature(aSketchFeature.numberOfSubs() - 1) -assert aLastFeature == aCircle, "ERROR: SketchMacroCircle has NOT expected to be valid" +assert aLastFeature.getKind() == "SketchMacroCircle", "ERROR: SketchMacroCircle has NOT expected to be valid" aDocument.removeFeature(aCircle) assert (aSketchFeature.numberOfSubs() == 12) diff --git a/src/SketchPlugin/Test/TestCreateCircleChangeType.py b/src/SketchPlugin/Test/TestCreateCircleChangeType.py index 92998894a..52daff262 100644 --- a/src/SketchPlugin/Test/TestCreateCircleChangeType.py +++ b/src/SketchPlugin/Test/TestCreateCircleChangeType.py @@ -49,6 +49,29 @@ def assertNotInitializedByThreePoints(theMacroCircle): assert (not aSecondPointRef.isInitialized()) assert (not aThirdPointRef.isInitialized()) +def getLastCircle(theSketch): + """ + obtains last feature from the sketch and generates error if the feature is not a circle + """ + expectedKind = "SketchCircle" + for anIndex in range(theSketch.numberOfSubs() - 1, -1, -1): + aSub = theSketch.subFeature(anIndex) + if (aSub.getKind() == expectedKind): + return aSub + +def verifyLastCircle(theSketch, theX, theY, theR): + """ + subroutine to verify position of last circle in the sketch + """ + aLastCircle = getLastCircle(theSketch) + aCenter = geomDataAPI_Point2D(aLastCircle.attribute("circle_center")) + verifyPointCoordinates(aCenter, theX, theY) + aRadius = aLastCircle.real("circle_radius") + assert aRadius.value() == theR, "Wrong radius {0}, expected {1}".format(aRadius.value(), theR) + +def verifyPointCoordinates(thePoint, theX, theY): + assert thePoint.x() == theX and thePoint.y() == theY, "Wrong '{0}' point ({1}, {2}), expected ({3}, {4})".format(thePoint.attributeType(), thePoint.x(), thePoint.y(), theX, theY) + def distancePointPoint(thePoint1, thePoint2): return thePoint1.pnt().distance(thePoint2.pnt()) @@ -160,7 +183,8 @@ aPassedPoint.setValue(aLineEnd.pnt()) aSession.finishOperation() aRadius = distancePointPoint(aLineStart, aLineEnd) -assert (aSketchFeature.numberOfSubs() == 2) +NB_FEATURES_EXPECTED = 4 # line, circle and two coincidences +assert (aSketchFeature.numberOfSubs() == NB_FEATURES_EXPECTED), "Number of features in sketch {}, expected {}".format(aSketchFeature.numberOfSubs(), NB_FEATURES_EXPECTED) verifyLastCircle(aSketchFeature, aLineStart.x(), aLineStart.y(), aRadius) #=========================================================================