From e466624749375b6ead3e8a8ded26bf29ea9325ae Mon Sep 17 00:00:00 2001 From: dish Date: Thu, 7 Dec 2023 10:03:46 +0000 Subject: [PATCH] [bos #35151][EDF](2023-T1) Centered rectangle. Central point is added to a sketch along with edges rectangle. --- .../Test/TestRectangleCentered.py | 79 +++++-- src/PythonAddons/macros/rectangle/feature.py | 222 +++++++++++------- src/SketchAPI/SketchAPI_Rectangle.cpp | 33 ++- src/SketchAPI/SketchAPI_Rectangle.h | 26 +- src/SketchAPI/SketchAPI_Sketch.cpp | 61 +++-- src/SketchAPI/SketchAPI_Sketch.h | 10 +- .../Test/TestRectangleCentered1.py | 174 ++++++++++++++ src/SketchPlugin/doc/examples/rectangle.py | 59 ++++- src/SketchPlugin/tests.set | 1 + 9 files changed, 526 insertions(+), 139 deletions(-) create mode 100644 src/SketchPlugin/Test/TestRectangleCentered1.py diff --git a/src/PythonAddons/Test/TestRectangleCentered.py b/src/PythonAddons/Test/TestRectangleCentered.py index e806f8be9..ae1fdbb26 100755 --- a/src/PythonAddons/Test/TestRectangleCentered.py +++ b/src/PythonAddons/Test/TestRectangleCentered.py @@ -38,13 +38,62 @@ def checkRectangle(lines, center, corner, tolerance = 1.e-7): ep_ref = points[i] assert(ep.distance(ep_ref) <= tolerance) -def checkRectangleL(lines, valref, tolerance = 1.e-5): - for i in range(0, 4): - line = SketchAPI_Line(lines[i]) - #print (line.defaultResult().shape().edge().length()) - #print (valref[i%2]) - #print (abs(line.defaultResult().shape().edge().length()-valref[i%2])) - assert(abs(line.defaultResult().shape().edge().length()-valref[i%2]) <= tolerance) +def areCounterDirectedAndOfEqualLength(theLineA, theLineB): + tolerance = 1.e-5 + + aStartA = theLineA.startPoint().pnt() + aEndA = theLineA.endPoint().pnt() + aDirA_X = aEndA.x() - aStartA.x() + aDirA_Y = aEndA.y() - aStartA.y() + + aStartB = theLineB.startPoint().pnt() + aEndB = theLineB.endPoint().pnt() + aDirB_X = aEndB.x() - aStartB.x() + aDirB_Y = aEndB.y() - aStartB.y() + + return abs(aDirA_X + aDirB_X) < tolerance and abs(aDirA_Y + aDirB_Y) < tolerance + +def arePerpendicular(theLineA, theLineB): + tolerance = 1.e-5 + + aStartA = theLineA.startPoint().pnt() + aEndA = theLineA.endPoint().pnt() + aDirA_X = aEndA.x() - aStartA.x() + aDirA_Y = aEndA.y() - aStartA.y() + aLengthA = theLineA.defaultResult().shape().edge().length() + + aStartB = theLineB.startPoint().pnt() + aEndB = theLineB.startPoint().pnt() + aDirB_X = aEndB.x() - aStartB.x() + aDirB_Y = aEndB.y() - aStartB.y() + aLengthB = theLineB.defaultResult().shape().edge().length() + + if (aLengthA < tolerance or aLengthB < tolerance): + return True + + return (aDirA_X * aDirB_X + aDirA_Y * aDirB_Y) / (aLengthA * aLengthB) < tolerance + +def areConnected(theLineA, theLineB): + aEndA = theLineA.endPoint().pnt() + aStartB = theLineB.startPoint().pnt() + return aEndA.x() == aStartB.x() and aEndA.y() == aStartB.y() + +def checkIfArbitraryAlignedRectangle(theEdgeLines): + aLine0 = SketchAPI_Line(theEdgeLines[0]) + aLine1 = SketchAPI_Line(theEdgeLines[1]) + aLine2 = SketchAPI_Line(theEdgeLines[2]) + aLine3 = SketchAPI_Line(theEdgeLines[3]) + + assert (areCounterDirectedAndOfEqualLength(aLine0, aLine2)) + assert (areCounterDirectedAndOfEqualLength(aLine1, aLine3)) + assert (arePerpendicular(aLine0, aLine1)) + assert (arePerpendicular(aLine2, aLine3)) + assert (areConnected(aLine0, aLine1)) + assert (areConnected(aLine1, aLine2)) + assert (areConnected(aLine2, aLine3)) + assert (areConnected(aLine3, aLine0)) + + model.begin() partSet = model.moduleDocument() @@ -80,34 +129,22 @@ checkRectangle(lines_3, SketchAPI_Line(lines_1[0]).startPoint().pnt(), SketchAPI # move center of rectangle SHIFT = 1.0 center = SketchAPI_Line(lines_1[0]).startPoint().pnt() -valref = [ \ -400.86931 , 200.78509 , \ -401.73886 , 201.57021 , \ -402.60865 , 202.35537 , \ -403.47866 , 203.14056 , \ -404.34890 , 203.92580 ] for i in range(0, 5): center.setX(center.x() + SHIFT) center.setY(center.y() + SHIFT) model.begin() sketch.move(SketchAPI_Line(lines_1[0]).startPoint(), center) model.end() - checkRectangleL(lines_3, valref[2*i:]) + checkIfArbitraryAlignedRectangle(lines_3) # move corner of rectangle corner = SketchAPI_Line(lines_2[0]).endPoint().pnt() -valref = [ \ -403.11209 , 202.95437 , \ -401.87551 , 201.98300 , \ -400.63915 , 201.01169 , \ -399.40301 , 200.04043 , \ -398.16710 , 199.06922 ] for i in range(0, 5): corner.setX(corner.x() + SHIFT) corner.setY(corner.y() + SHIFT) model.begin() sketch.move(SketchAPI_Line(lines_2[0]).endPoint(), corner) model.end() - checkRectangleL(lines_3, valref[2*i:]) + checkIfArbitraryAlignedRectangle(lines_3) assert(model.checkPythonDump()) diff --git a/src/PythonAddons/macros/rectangle/feature.py b/src/PythonAddons/macros/rectangle/feature.py index 991c55a04..d2df54356 100755 --- a/src/PythonAddons/macros/rectangle/feature.py +++ b/src/PythonAddons/macros/rectangle/feature.py @@ -106,15 +106,15 @@ class SketchPlugin_Rectangle(model.Feature): """Override Feature.initAttributes()""" # Flag whether the rectangle is accessory self.data().addAttribute(self.AUXILIARY_ID(), ModelAPI.ModelAPI_AttributeBoolean.typeId()) - # Creating corners of the rectangle + # Corners of the rectangle (being defined by opposite corners) self.data().addAttribute(self.START_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId()) self.data().addAttribute(self.END_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId()) - # Creating list to store lines + # List with both contour and diagonal lines self.data().addAttribute(self.LINES_LIST_ID(), ModelAPI.ModelAPI_AttributeRefList.typeId()) ModelAPI.ModelAPI_Session.get().validators().registerNotObligatory(self.getKind(), self.LINES_LIST_ID()) # Type of rectangle self.data().addAttribute(self.RECTANGLE_TYPE_ID(), ModelAPI.ModelAPI_AttributeString.typeId()) - # Center and corner of the rectangle + # Center and corner of centered rectangle (being defined by center and corner) self.data().addAttribute(self.CENTER_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId()) self.data().addAttribute(self.CORNER_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId()) @@ -128,144 +128,191 @@ class SketchPlugin_Rectangle(model.Feature): """ return True -# Edition of the rectangle + +# Editing of a rectangle. It is called on select of the first generative point +# and on hover of the second generative point. Generative point is either corner or center, +# depending on rectangle type. And also it is called on call of Sketch.addRectangleCentered(...) in TUI. def execute(self): - # Retrieving list of already created lines - aLinesList = self.reflist(self.LINES_LIST_ID()) - aNbLines = aLinesList.size() - if aNbLines == 1: - # Create 1-4 lines to compose the rectangle - for i in range (0, 3): + # Retrieve list of already created lines + aLinesList = self.reflist(self.LINES_LIST_ID()) + if aLinesList.size() == 1: + # Create remaining rectangle contour lines + for i in range (1, 4): aLine = self.__sketch.addFeature("SketchLine") aLinesList.append(aLine) - self.updateLines() - aNbLines = aLinesList.size() + self.__updateLines() + + # Connect rectangle contour lines aStartPoints = [] - # Create constraints to keep the rectangle - for i in range (0, aNbLines): + for i in range (0, 4): aLine = ModelAPI.objectToFeature(aLinesList.object(i)) - # connect neighbor lines by coincidence - iPrev = i - 1 - if iPrev < 0: - iPrev = aNbLines - 1 + iPrev = i - 1 if i != 0 else 3 aPrevLine = ModelAPI.objectToFeature(aLinesList.object(iPrev)) aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence") - aRefAttrA = aCoincidence.refattr("ConstraintEntityA") - aRefAttrB = aCoincidence.refattr("ConstraintEntityB") - aRefAttrA.setAttr(aPrevLine.attribute("EndPoint")) - aRefAttrB.setAttr(aLine.attribute("StartPoint")) + aCoincidence.refattr("ConstraintEntityA").setAttr(aPrevLine.attribute("EndPoint")) + aCoincidence.refattr("ConstraintEntityB").setAttr(aLine.attribute("StartPoint")) aStartPoints.append(aLine.attribute("StartPoint")) - # Flags which show perpendicular constraint is build for correponding line - self.__isPERP = [False, False, False] + # Update coordinates of created lines - self.updateLines() + self.__updateLines() + + # Flags, indicating perpendicular constraint is imposed on contour lines i and i+1. + self.__isPERP = [False, False, False] + # Create auxiliary diagonals in case of centered rectangle if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID(): aDiag1 = self.__sketch.addFeature("SketchLine") aLinesList.append(aDiag1) aDiag2 = self.__sketch.addFeature("SketchLine") aLinesList.append(aDiag2) - # coincidences in corners + + # Add coincidences between diagonals' endpoints and rectangle vertices aPoints = [aDiag1.attribute("StartPoint"), aDiag2.attribute("StartPoint"), aDiag1.attribute("EndPoint"), aDiag2.attribute("EndPoint")] + for i in range (0, len(aPoints)): aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence") - aRefAttrA = aCoincidence.refattr("ConstraintEntityA") - aRefAttrB = aCoincidence.refattr("ConstraintEntityB") - aRefAttrA.setAttr(aStartPoints[i]) - aRefAttrB.setAttr(aPoints[i]) + aCoincidence.refattr("ConstraintEntityA").setAttr(aStartPoints[i]) + aCoincidence.refattr("ConstraintEntityB").setAttr(aPoints[i]) + # Update coordinates of created lines - self.updateLines() + self.__updateLines() aDiag1.execute() aDiag2.execute() + # coincidences between center point and diagonals - refPnt = self.getReferencePoint(self.refattr(self.CENTER_REF_ID())) - if refPnt is not None: + attr = self.__getPoint2DAttrOfSketchPoint(self.refattr(self.CENTER_REF_ID())) + if attr is not None: for line in [aDiag1.lastResult(), aDiag2.lastResult()]: aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence") - aCoincidence.refattr("ConstraintEntityA").setAttr(refPnt) + aCoincidence.refattr("ConstraintEntityA").setAttr(attr) aCoincidence.refattr("ConstraintEntityB").setObject(line) - # Perpendicular for the lines which already have result + + # Add perpendicular constraints to the contour lines, which already have result for i in range (0, 3): if self.__isPERP[i]: continue + aLine_A = ModelAPI.objectToFeature(aLinesList.object(i)) aLineResult_A = aLine_A.lastResult() if aLineResult_A is None: continue + aLine_B = ModelAPI.objectToFeature(aLinesList.object(i+1)) aLineResult_B = aLine_B.lastResult() if aLineResult_B is None: continue - aHVConstraint = self.__sketch.addFeature("SketchConstraintPerpendicular") - refattrA = aHVConstraint.refattr("ConstraintEntityA") - refattrA.setObject(aLine_A.lastResult()) - refattrB = aHVConstraint.refattr("ConstraintEntityB") - refattrB.setObject(aLine_B.lastResult()) + + aConstraintPerp = self.__sketch.addFeature("SketchConstraintPerpendicular") + aConstraintPerp.refattr("ConstraintEntityA").setObject(aLine_A.lastResult()) + aConstraintPerp.refattr("ConstraintEntityB").setObject(aLine_B.lastResult()) self.__isPERP[i] = True + def attributeChanged(self, theID): if theID == self.START_ID() or theID == self.END_ID() or theID == self.CENTER_ID() or theID == self.CENTER_REF_ID() or theID == self.CORNER_ID(): - # Search the sketch containing this rectangle + # Find the sketch containing this rectangle self.__sketch = None - aRefs = self.data().refsToMe(); - for iter in aRefs: - aFeature = ModelAPI.objectToFeature(iter.owner()) + aRefsToMe = self.data().refsToMe() + for aRefToMe in aRefsToMe: + aFeature = ModelAPI.objectToFeature(aRefToMe.owner()) if aFeature.getKind() == "Sketch": self.__sketch = ModelAPI.featureToCompositeFeature(aFeature) break + if theID == self.CENTER_ID(): + aCenter = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CENTER_ID())) # shared_ptr to Point2D + aCenterSketchPointAttr = self.refattr(self.CENTER_REF_ID()) + if (not aCenterSketchPointAttr.isInitialized()): + # Create SketchPoint. In .execute() it will be constrained to keep center. + aCenterSketchPoint = self.__sketch.addFeature("SketchPoint") + aCenterSketchPoint.data().boolean("Auxiliary").setValue(True) + aCenterSketchPointCoords = GeomDataAPI.geomDataAPI_Point2D(aCenterSketchPoint.attribute("PointCoordinates")) # shared_ptr to Point2D + aCenterSketchPointCoords.setValue(aCenter.x(), aCenter.y()) + aCenterSketchPointAttr.setObject(aCenterSketchPoint) + else: + # Maintain consistency between center SketchPoint and center Point2D. + aCenterSketchPointCoordsAttr = self.__getPoint2DAttrOfSketchPoint(aCenterSketchPointAttr) + if (aCenterSketchPointCoordsAttr == None): + Warning("Faulty logic") + else: + aCenterSketchPointCoords = GeomDataAPI.geomDataAPI_Point2D(aCenterSketchPointCoordsAttr) # shared_ptr to Point2D + aCenterSketchPointCoords.setValue(aCenter.x(), aCenter.y()) + elif theID == self.CENTER_REF_ID(): + aCenterSketchPointAttr = self.refattr(self.CENTER_REF_ID()) + aCenterSketchPointCoordsAttr = self.__getPoint2DAttrOfSketchPoint(aCenterSketchPointAttr) # shared_ptr to Point2D + if (aCenterSketchPointCoordsAttr == None): + Warning("Faulty logic. Attempt to set rectangle's attribute " + self.CENTER_REF_ID() + " not with refattr to SketchPoint.") + return + + # Maintain consistency between center SketchPoint and center Point2D. + aCenterSketchPointCoords = GeomDataAPI.geomDataAPI_Point2D(aCenterSketchPointCoordsAttr) # shared_ptr to Point2D + aCenter = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CENTER_ID())) # shared_ptr to Point2D + aCenter.setValue(aCenterSketchPointCoords.x(), aCenterSketchPointCoords.y()) + aLinesList = self.reflist(self.LINES_LIST_ID()) aNbLines = aLinesList.size() if aNbLines == 0: - # Create first line to be able to create a coincidence with selected point/feature - for i in range (0, 1): - aLine = self.__sketch.addFeature("SketchLine") - aLinesList.append(aLine) + # If only one generative point is iniialized, + # do not create the full set of contour lines to not clutter + # UI with icons of constraints near the mouse pointer. + aLine = self.__sketch.addFeature("SketchLine") + aLinesList.append(aLine) aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID())) aEndPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.END_ID())) - aCenter = self.getPointByRef(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID())) + aCenter = self.__getPoint2D(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID())) aCorner = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CORNER_ID())) + if (aStartPoint.isInitialized() and aEndPoint.isInitialized()) or (aCenter is not None and aCorner.isInitialized()): - self.updateLines() + self.__updateLines() else: - self.updateStartPoint() - if theID == self.AUXILIARY_ID(): + self.__updateLinesWithOnlyGenerativePoint() + elif theID == self.AUXILIARY_ID(): + # Change aux attribute of contour lines anAuxiliary = self.data().boolean(self.AUXILIARY_ID()).value() - aLinesList = self.reflist(self.LINES_LIST_ID()) - aNbLines = aLinesList.size() - # Update coordinates of rectangle lines - for i in range (0, aNbLines): + aLinesList = self.reflist(self.LINES_LIST_ID()) + for i in range (0, min(aLinesList.size(), 4)): aLine = ModelAPI.objectToFeature(aLinesList.object(i)) aLine.data().boolean("Auxiliary").setValue(anAuxiliary) - - def getReferencePoint(self, theRef): - if theRef.isObject() and theRef.object() is not None: - feature = ModelAPI.ModelAPI_Feature.feature(theRef.object()) + elif theID == self.RECTANGLE_TYPE_ID(): + # TODO Find a way to distinguish "attribute changed" events on hover and on click. + # Now, if both generative points are selected, but the rectangle is not applied, + # and then rectangle type is changed, the unapplied rectangle is erased. + # It should be applied instead. + aLinesList = self.reflist(self.LINES_LIST_ID()).clear() + aCenterSketchPointAttr = self.refattr(self.CENTER_REF_ID()) + aCenterSketchPointAttr.reset() + self.attribute(self.START_ID()).reset() + self.attribute(self.END_ID()).reset() + self.attribute(self.CENTER_ID()).reset() + self.attribute(self.CORNER_ID()).reset() + + + def __getPoint2DAttrOfSketchPoint(self, theSketchPointRefAttr): + if theSketchPointRefAttr.isObject() and theSketchPointRefAttr.object() is not None: + feature = ModelAPI.ModelAPI_Feature.feature(theSketchPointRefAttr.object()) if feature.getKind() == "SketchPoint": return feature.attribute("PointCoordinates") else: - return theRef.attr() - return None - - def getPointByRef(self, thePoint, theRef): - attr = thePoint - if theRef.isInitialized(): - refPnt = self.getReferencePoint(theRef) - if refPnt is not None: - attr = refPnt + return theSketchPointRefAttr.attr() + + + def __getPoint2D(self, thePoint2DAttr, theSketchPointRefAttr): + attr = thePoint2DAttr + if theSketchPointRefAttr.isInitialized(): + aPoint2DAttr = self.__getPoint2DAttrOfSketchPoint(theSketchPointRefAttr) + if aPoint2DAttr is not None: + attr = aPoint2DAttr if attr is None or not attr.isInitialized(): return None return GeomDataAPI.geomDataAPI_Point2D(attr).pnt() - def updateLines(self): - # Retrieving list of already created lines - aLinesList = self.reflist(self.LINES_LIST_ID()) - aNbLines = min(aLinesList.size(), 4) + + def __updateLines(self): if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID(): - aCenter = self.getPointByRef(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID())) + aCenter = self.__getPoint2D(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID())) aCorner = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CORNER_ID())) aStartX = 2.0 * aCenter.x() - aCorner.x() aStartY = 2.0 * aCenter.y() - aCorner.y() @@ -275,16 +322,20 @@ class SketchPlugin_Rectangle(model.Feature): aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID())) aEndPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.END_ID())) aX = [aStartPoint.x(), aStartPoint.x(), aEndPoint.x(), aEndPoint.x()] - aY = [aStartPoint.y(), aEndPoint.y(), aEndPoint.y(), aStartPoint.y()] - anAuxiliary = self.data().boolean(self.AUXILIARY_ID()).value() + aY = [aStartPoint.y(), aEndPoint.y(), aEndPoint.y(), aStartPoint.y()] + + # Retrieve list of already created lines + aLinesList = self.reflist(self.LINES_LIST_ID()) + aNumOfContourLines = min(aLinesList.size(), 4) - # do not recalculate the rectrangle after each update + # Do not update lines during update of coordinates wasBlocked = [] for i in range (0, aLinesList.size()): wasBlocked.append(aLinesList.object(i).data().blockSendAttributeUpdated(True)) # Update coordinates of rectangle lines - for i in range (0, aNbLines): + anAuxiliary = self.data().boolean(self.AUXILIARY_ID()).value() + for i in range (0, aNumOfContourLines): aLine = ModelAPI.objectToFeature(aLinesList.object(i)) aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("StartPoint")) aLineEnd = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint")) @@ -294,20 +345,21 @@ class SketchPlugin_Rectangle(model.Feature): # Update auxiliary diagonals if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID(): - for i in range (aNbLines, aLinesList.size()): + for i in range (aNumOfContourLines, aLinesList.size()): aLine = ModelAPI.objectToFeature(aLinesList.object(i)) aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("StartPoint")) aLineEnd = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint")) - aLineStart.setValue(aX[i-aNbLines-1], aY[i-aNbLines-1]) - aLineEnd.setValue(aX[i-aNbLines+1], aY[i-aNbLines+1]) + aLineStart.setValue(aX[i-aNumOfContourLines-1], aY[i-aNumOfContourLines-1]) + aLineEnd.setValue(aX[i-aNumOfContourLines+1], aY[i-aNumOfContourLines+1]) aLine.data().boolean("Auxiliary").setValue(True) - # update the rectangle + # Update the rectangle for i in range (0, aLinesList.size()): aLinesList.object(i).data().blockSendAttributeUpdated(wasBlocked[i], True) - def updateStartPoint(self): - # Retrieving list of already created lines + + def __updateLinesWithOnlyGenerativePoint(self): + # Retrieve list of already created lines aLinesList = self.reflist(self.LINES_LIST_ID()) aNbLines = aLinesList.size() @@ -316,7 +368,7 @@ class SketchPlugin_Rectangle(model.Feature): aX = aStartPoint.x() aY = aStartPoint.y() else: - aCenter = self.getPointByRef(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID())) + aCenter = self.__getPoint2D(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID())) aX = aCenter.x() aY = aCenter.y() diff --git a/src/SketchAPI/SketchAPI_Rectangle.cpp b/src/SketchAPI/SketchAPI_Rectangle.cpp index 303a3bcf0..cd1e2c233 100644 --- a/src/SketchAPI/SketchAPI_Rectangle.cpp +++ b/src/SketchAPI/SketchAPI_Rectangle.cpp @@ -34,21 +34,24 @@ SketchAPI_Rectangle::SketchAPI_Rectangle( } SketchAPI_Rectangle::SketchAPI_Rectangle(const std::shared_ptr & theFeature, - double theX1, double theY1, double theX2, double theY2) + double theX1, double theY1, + double theX2, double theY2, + bool theCreateByCenterAndCorner) : SketchAPI_SketchEntity(theFeature) { if (initialize()) { - setByCoordinates(theX1, theY1, theX2, theY2); + theCreateByCenterAndCorner ? setByCenterAndCornerCoords(theX1, theY1, theX2, theY2) : setByCoordinates(theX1, theY1, theX2, theY2); } } SketchAPI_Rectangle::SketchAPI_Rectangle(const std::shared_ptr & theFeature, - const std::shared_ptr & theFirstPoint, - const std::shared_ptr & theEndPoint) + const std::shared_ptr & thePoint1, + const std::shared_ptr & thePoint2, + bool theCreateByCenterAndCorner) : SketchAPI_SketchEntity(theFeature) { if (initialize()) { - setByPoints(theFirstPoint, theEndPoint); + theCreateByCenterAndCorner ? setByCenterAndCornerPoints(thePoint1, thePoint2) : setByPoints(thePoint1, thePoint2); } } @@ -75,6 +78,26 @@ void SketchAPI_Rectangle::setByPoints(const std::shared_ptr & the execute(); } +void SketchAPI_Rectangle::setByCenterAndCornerCoords( + double theCenterX, double theCenterY, + double theCornerX, double theCornerY +) { + fillAttribute("RectangleTypeCentered", type()); + fillAttribute(centerPoint(), theCenterX, theCenterY); + fillAttribute(cornerPoint(), theCornerX, theCornerY); + execute(); +} + +void SketchAPI_Rectangle::setByCenterAndCornerPoints( + const std::shared_ptr & theCenterPoint, + const std::shared_ptr & theCornerPoint +) { + fillAttribute("RectangleTypeCentered", type()); + fillAttribute(theCenterPoint, centerPoint()); + fillAttribute(theCornerPoint, cornerPoint()); + execute(); +} + //-------------------------------------------------------------------------------------- std::list > SketchAPI_Rectangle::lines() const diff --git a/src/SketchAPI/SketchAPI_Rectangle.h b/src/SketchAPI/SketchAPI_Rectangle.h index b504729ea..c570ddeb2 100644 --- a/src/SketchAPI/SketchAPI_Rectangle.h +++ b/src/SketchAPI/SketchAPI_Rectangle.h @@ -41,12 +41,18 @@ public: /// Constructor with values SKETCHAPI_EXPORT SketchAPI_Rectangle(const std::shared_ptr & theFeature, - double theX1, double theY1, double theX2, double theY2); + double theX1, double theY1, + double theX2, double theY2, + bool theCreateByCenterAndCorner = false); /// Constructor with values SKETCHAPI_EXPORT SketchAPI_Rectangle(const std::shared_ptr & theFeature, - const std::shared_ptr & theFirstPoint, - const std::shared_ptr & theEndPoint); + const std::shared_ptr & thePoint1, + const std::shared_ptr & thePoint2, + bool theCreateByCenterAndCorner = false); + + + /// Destructor SKETCHAPI_EXPORT virtual ~SketchAPI_Rectangle(); @@ -77,6 +83,20 @@ public: void setByPoints(const std::shared_ptr & theFirstPoint, const std::shared_ptr & theSecondPoint); + /// Set by coordinates + SKETCHAPI_EXPORT + void setByCenterAndCornerCoords( + double theCenterX, double theCenterY, + double theCornerX, double theCornerY + ); + + /// Set by points + SKETCHAPI_EXPORT + void setByCenterAndCornerPoints( + const std::shared_ptr & theCenterPoint, + const std::shared_ptr & theCornerPoint + ); + /// List of lines composing rectangle SKETCHAPI_EXPORT std::list > lines() const; }; diff --git a/src/SketchAPI/SketchAPI_Sketch.cpp b/src/SketchAPI/SketchAPI_Sketch.cpp index 028b1b2d3..b3b5b2251 100644 --- a/src/SketchAPI/SketchAPI_Sketch.cpp +++ b/src/SketchAPI/SketchAPI_Sketch.cpp @@ -533,18 +533,9 @@ std::shared_ptr SketchAPI_Sketch::addLine(const std::wstring & t std::shared_ptr SketchAPI_Sketch::addRectangle(double theX1, double theY1, double theX2, double theY2) { - std::shared_ptr aFeature = - compositeFeature()->addFeature(SketchAPI_Rectangle::ID()); + std::shared_ptr aFeature = compositeFeature()->addFeature(SketchAPI_Rectangle::ID()); return RectanglePtr(new SketchAPI_Rectangle(aFeature, theX1, theY1, theX2, theY2)); } -std::shared_ptr SketchAPI_Sketch::addRectangle( - const std::shared_ptr & theStartPoint, - const std::shared_ptr & theEndPoint) -{ - std::shared_ptr aFeature = - compositeFeature()->addFeature(SketchAPI_Rectangle::ID()); - return RectanglePtr(new SketchAPI_Rectangle(aFeature, theStartPoint, theEndPoint)); -} static std::shared_ptr pointCoordinates( const std::pair, ModelHighAPI_RefAttr> & thePoint) @@ -559,29 +550,57 @@ static std::shared_ptr pointCoordinates( anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID()); } - std::shared_ptr aPntAttr = - std::dynamic_pointer_cast(anAttr); + std::shared_ptr aPntAttr = std::dynamic_pointer_cast(anAttr); if (aPntAttr) return aPntAttr->pnt(); return std::shared_ptr(); } +std::shared_ptr SketchAPI_Sketch::addRectangle( + const std::pair, ModelHighAPI_RefAttr> & theStartPoint, + const std::pair, ModelHighAPI_RefAttr> & theEndPoint) +{ + std::shared_ptr aFeature = compositeFeature()->addFeature(SketchAPI_Rectangle::ID()); + RectanglePtr aRect(new SketchAPI_Rectangle(aFeature)); + fillAttribute("RectangleTypeByCorners", aRect->type()); + fillAttribute(pointCoordinates(theStartPoint), aRect->startPoint()); + fillAttribute(pointCoordinates(theEndPoint), aRect->endPoint()); + aRect->execute(); + + if (!theStartPoint.second.isEmpty() && aRect->linesList()->size() >= 1) { + // Get end point of the first line in rectangle and apply coincidence constraint + FeaturePtr aLine = ModelAPI_Feature::feature(aRect->linesList()->object(0)); + AttributePtr aLinePnt = aLine->attribute(SketchPlugin_Line::END_ID()); + setCoincident(ModelHighAPI_RefAttr(aLinePnt), theStartPoint.second); + } + + if (!theEndPoint.second.isEmpty() && aRect->linesList()->size() >= 4) { + // Get start point of the last line in rectangle and apply coincidence constraint + FeaturePtr aLine = ModelAPI_Feature::feature(aRect->linesList()->object(3)); + AttributePtr aLinePnt = aLine->attribute(SketchPlugin_Line::START_ID()); + setCoincident(ModelHighAPI_RefAttr(aLinePnt), theEndPoint.second); + } + return aRect; +} + std::shared_ptr SketchAPI_Sketch::addRectangleCentered( const std::pair, ModelHighAPI_RefAttr> & theCenter, const std::pair, ModelHighAPI_RefAttr> & theCorner) { - std::shared_ptr aFeature = - compositeFeature()->addFeature(SketchAPI_Rectangle::ID()); + std::shared_ptr aFeature = compositeFeature()->addFeature(SketchAPI_Rectangle::ID()); RectanglePtr aRect(new SketchAPI_Rectangle(aFeature)); fillAttribute("RectangleTypeCentered", aRect->type()); + if (!theCenter.second.isEmpty()) fillAttribute(theCenter.second, aRect->centerPointRef()); - fillAttribute(pointCoordinates(theCenter), aRect->centerPoint()); + else + fillAttribute(pointCoordinates(theCenter), aRect->centerPoint()); + fillAttribute(pointCoordinates(theCorner), aRect->cornerPoint()); aRect->execute(); - if (!theCorner.second.isEmpty() && aRect->linesList()->size() > 1) { - // get start point of the last line in rectangle and apply coindidence constraint + if (!theCorner.second.isEmpty() && aRect->linesList()->size() >= 4) { + // get start point of the last line in rectangle and apply coincidence constraint FeaturePtr aLine = ModelAPI_Feature::feature(aRect->linesList()->object(3)); AttributePtr aEndPnt = aLine->attribute(SketchPlugin_Line::START_ID()); setCoincident(ModelHighAPI_RefAttr(aEndPnt), theCorner.second); @@ -589,6 +608,14 @@ std::shared_ptr SketchAPI_Sketch::addRectangleCentered( return aRect; } +std::shared_ptr SketchAPI_Sketch::addRectangleCentered( + double theCenterX, double theCenterY, + double theCornerX, double theCornerY +) { + std::shared_ptr aFeature = compositeFeature()->addFeature(SketchAPI_Rectangle::ID()); + return RectanglePtr(new SketchAPI_Rectangle(aFeature, theCenterX, theCenterY, theCornerX, theCornerY, true)); +} + //-------------------------------------------------------------------------------------- std::shared_ptr SketchAPI_Sketch::addCircle(double theCenterX, double theCenterY, diff --git a/src/SketchAPI/SketchAPI_Sketch.h b/src/SketchAPI/SketchAPI_Sketch.h index f9eea73cb..fffa685ca 100644 --- a/src/SketchAPI/SketchAPI_Sketch.h +++ b/src/SketchAPI/SketchAPI_Sketch.h @@ -179,13 +179,19 @@ public: /// Add rectangle SKETCHAPI_EXPORT std::shared_ptr addRectangle( - const std::shared_ptr & theStartPoint, - const std::shared_ptr & theEndPoint); + const std::pair, ModelHighAPI_RefAttr> & theStartPoint, + const std::pair, ModelHighAPI_RefAttr> & theEndPoint); /// Add rectangle SKETCHAPI_EXPORT std::shared_ptr addRectangleCentered( const std::pair, ModelHighAPI_RefAttr> & theCenter, const std::pair, ModelHighAPI_RefAttr> & theCorner); + /// Add rectangle + SKETCHAPI_EXPORT + std::shared_ptr addRectangleCentered( + double theCenterX, double theCenterY, + double theCornerX, double theCornerY + ); /// Add circle SKETCHAPI_EXPORT diff --git a/src/SketchPlugin/Test/TestRectangleCentered1.py b/src/SketchPlugin/Test/TestRectangleCentered1.py new file mode 100644 index 000000000..86c3b9e1a --- /dev/null +++ b/src/SketchPlugin/Test/TestRectangleCentered1.py @@ -0,0 +1,174 @@ +# Copyright (C) 2014-2023 CEA, EDF +# +# 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 +# + +""" + TestRectangle.py + Unit test of SketchPlugin_Rectangle class (centered rectangle case) + +""" +from GeomDataAPI import * +from ModelAPI import * +import math +from salome.shaper import model + +#========================================================================= +# Initialization of the test +#========================================================================= + +__updated__ = "2023-08-07" + + +#========================================================================= +# Auxiliary functions +#========================================================================= +def isHorizontal(line): + aStart = geomDataAPI_Point2D(line.attribute("StartPoint")) + aEnd = geomDataAPI_Point2D(line.attribute("EndPoint")) + return aStart.y() == aEnd.y() + +def isVertical(line): + aStart = geomDataAPI_Point2D(line.attribute("StartPoint")) + aEnd = geomDataAPI_Point2D(line.attribute("EndPoint")) + return aStart.x() == aEnd.x() + +def areCounterDirectedAndOfEqualLength(theLineA, theLineB): + tolerance = 1.e-5 + + aStartA = geomDataAPI_Point2D(theLineA.attribute("StartPoint")) + aEndA = geomDataAPI_Point2D(theLineA.attribute("EndPoint")) + aDirA_X = aEndA.x() - aStartA.x() + aDirA_Y = aEndA.y() - aStartA.y() + + aStartB = geomDataAPI_Point2D(theLineB.attribute("StartPoint")) + aEndB = geomDataAPI_Point2D(theLineB.attribute("EndPoint")) + aDirB_X = aEndB.x() - aStartB.x() + aDirB_Y = aEndB.y() - aStartB.y() + + return abs(aDirA_X + aDirB_X) < tolerance and abs(aDirA_Y + aDirB_Y) < tolerance + +def arePerpendicular(theLineA, theLineB): + tolerance = 1.e-5 + + aStartA = geomDataAPI_Point2D(theLineA.attribute("StartPoint")) + aEndA = geomDataAPI_Point2D(theLineA.attribute("EndPoint")) + aDirA_X = aEndA.x() - aStartA.x() + aDirA_Y = aEndA.y() - aStartA.y() + aLengthA = theLineA.lastResult().shape().edge().length() + + aStartB = geomDataAPI_Point2D(theLineB.attribute("StartPoint")) + aEndB = geomDataAPI_Point2D(theLineB.attribute("EndPoint")) + aDirB_X = aEndB.x() - aStartB.x() + aDirB_Y = aEndB.y() - aStartB.y() + aLengthB = theLineB.lastResult().shape().edge().length() + + if (aLengthA < tolerance or aLengthB < tolerance): + return True + + return (aDirA_X * aDirB_X + aDirA_Y * aDirB_Y) / (aLengthA * aLengthB) < tolerance + +def areConnected(theLineA, theLineB): + aEndA = geomDataAPI_Point2D(theLineA.attribute("EndPoint")) + aStartB = geomDataAPI_Point2D(theLineB.attribute("StartPoint")) + return aEndA.x() == aStartB.x() and aEndA.y() == aStartB.y() + + +#========================================================================= +# Start of test +#========================================================================= +aSession = ModelAPI_Session.get() +aDocument = aSession.moduleDocument() +#========================================================================= +# Creation of a sketch +#========================================================================= +aSession.startOperation() +aSketchFeature = featureToCompositeFeature(aDocument.addFeature("Sketch")) +origin = geomDataAPI_Point(aSketchFeature.attribute("Origin")) +origin.setValue(0, 0, 0) +dirx = geomDataAPI_Dir(aSketchFeature.attribute("DirX")) +dirx.setValue(1, 0, 0) +norm = geomDataAPI_Dir(aSketchFeature.attribute("Norm")) +norm.setValue(0, 0, 1) +aSession.finishOperation() +#========================================================================= +# Create a rectangle +#========================================================================= +aSession.startOperation() +aRectangle = aSketchFeature.addFeature("SketchRectangle") +aRectangle.string("RectangleType").setValue("RectangleTypeCentered") +aCenter = geomDataAPI_Point2D(aRectangle.attribute("RectCenterPoint")) +aCorner = geomDataAPI_Point2D(aRectangle.attribute("RectCornerPoint")) +aCenter.setValue(10., 10.) +aCorner.setValue(40., 30.) +aSession.finishOperation() +#========================================================================= +# Check the lines of rectangle are parallel to the axes +#========================================================================= +aNbSubs = aSketchFeature.numberOfSubs() +assert (aNbSubs >= 5) # The first feature on the sketch is center SketchPoint. +aNbLines = 0 +for i in range (1, 5): + aFeature = objectToFeature(aSketchFeature.subFeature(i)) + if aFeature.getKind() == "SketchLine": + aLastLine = aFeature + assert (isHorizontal(aLastLine) or isVertical(aLastLine)) + aNbLines = aNbLines + 1 +assert (aNbLines == 4) +assert (model.dof(aSketchFeature) == 5) +#========================================================================= +# Move one of lines +#========================================================================= +aSession.startOperation() +aLineEnd = geomDataAPI_Point2D(aLastLine.attribute("EndPoint")) +aLineEnd.setValue(41., 30.) +aSession.finishOperation() + +#========================================================================= +# Check the opposites lines of rectangle are parallel, and neighboring +# ones are perpendicular +#========================================================================= +aLine0 = objectToFeature(aSketchFeature.subFeature(1)) +assert (aLine0.getKind() == "SketchLine") + +aLine1 = objectToFeature(aSketchFeature.subFeature(2)) +assert (aLine1.getKind() == "SketchLine") + +aLine2 = objectToFeature(aSketchFeature.subFeature(3)) +assert (aLine2.getKind() == "SketchLine") + +aLine3 = objectToFeature(aSketchFeature.subFeature(4)) +assert (aLine3.getKind() == "SketchLine") + +assert (areCounterDirectedAndOfEqualLength(aLine0, aLine2)) +assert (areCounterDirectedAndOfEqualLength(aLine1, aLine3)) +assert (arePerpendicular(aLine0, aLine1)) +assert (arePerpendicular(aLine2, aLine3)) + +#========================================================================= +# Check the contour is closed +#========================================================================= +assert (areConnected(aLine0, aLine1)) +assert (areConnected(aLine1, aLine2)) +assert (areConnected(aLine2, aLine3)) +assert (areConnected(aLine3, aLine0)) + +#========================================================================= +# End of test +#========================================================================= + +assert(model.checkPythonDump()) diff --git a/src/SketchPlugin/doc/examples/rectangle.py b/src/SketchPlugin/doc/examples/rectangle.py index 81d8910ff..596e076b6 100644 --- a/src/SketchPlugin/doc/examples/rectangle.py +++ b/src/SketchPlugin/doc/examples/rectangle.py @@ -1,16 +1,63 @@ +#!/usr/bin/env python +"""Un exemple de création d'un rectangle""" + from salome.shaper import model from salome.shaper import geom + 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")) -Rectangle_1 = Sketch_1.addRectangle(5.5, 8.5, 31.3, 78.9) -# rectangle from center and end points -center = geom.Pnt2d(10, 5) -corner = geom.Pnt2d(25, 75) -rectangle_2 = sketch.addRectangleCentered(center, corner) + + +### Rectangle by corners defined with doubles. +rectWithFloats = Sketch_1.addRectangle(-8, -8, -5, -3) +############################################################ + + +### Rectangle by corners defined with SketchPoints. +SP_corner1 = Sketch_1.addPoint(-8, 8) +SP_corner2 = Sketch_1.addPoint(-5, 3) + +rectWithSPs = Sketch_1.addRectangle(SP_corner1, SP_corner2) +############################################################ + + +### Rectangle by corners defined with Pnt2Ds. +Pnt2D_corner1 = geom.Pnt2d(-8 - 5, 8) +Pnt2D_corner2 = geom.Pnt2d(-5 - 5, 3) + +rectWithPnt2Ds = Sketch_1.addRectangle(Pnt2D_corner1, Pnt2D_corner2) +############################################################ + + +############################################################ +############################################################ +### Rectangle by center and corner defined with Pnt2Ds. +Pnt2D_center = geom.Pnt2d(6.5 + 5, 5.5) +Pnt2D_corner = geom.Pnt2d(8.0 + 5, 8.0) + +rectCenteredWithPnt2Ds = Sketch_1.addRectangleCentered(Pnt2D_center, Pnt2D_corner) +############################################################ + + +### Rectangle by center and corner defined with SketchPoints. +SP_center = Sketch_1.addPoint(6.5, 5.5) +SP_corner = Sketch_1.addPoint(8.0, 8.0) + +rectCenteredWithPnt2Ds = Sketch_1.addRectangleCentered(SP_center, SP_corner) +############################################################ + + +### Rectangle by center and corner defined with doubles. +rectCenteredWithDoubles = Sketch_1.addRectangleCentered(6.5, -5.5, 8.0, -8.0) +############################################################ model.do() -model.end() +model.end() \ No newline at end of file diff --git a/src/SketchPlugin/tests.set b/src/SketchPlugin/tests.set index e737708c0..a6c4c8743 100644 --- a/src/SketchPlugin/tests.set +++ b/src/SketchPlugin/tests.set @@ -182,6 +182,7 @@ SET(TEST_NAMES_PARA TestProjectionUpdate.py TestProjectionWithoutReference.py TestRectangle1.py + TestRectangleCentered1.py TestRemainingDoF.py TestRemoveBSpline.py TestRemoveBSplinePeriodic.py -- 2.39.2