]> SALOME platform Git repositories - modules/shaper.git/blob - src/PythonAddons/macros/rectangle/feature.py
Salome HOME
[GITHUB #2] problem in export dialog - difference in STEP vs BREP
[modules/shaper.git] / src / PythonAddons / macros / rectangle / feature.py
1 # Copyright (C) 2014-2024  CEA, EDF
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 """
21 Macro-feature to produce rectangle in the sketcher
22 Author: Artem ZHIDKOV
23 """
24
25 from salome.shaper import model
26 import ModelAPI
27 import GeomDataAPI
28
29 class SketchPlugin_Rectangle(model.Feature):
30     """
31     Implementation of rectangle creation.
32
33     It produced 2 horizontal lines and 2 vertical lines connected by coincidence constraints
34     """
35
36 # Initializations
37
38     def __init__(self):
39         """x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""
40         model.Feature.__init__(self)
41
42     @staticmethod
43     def ID():
44         """Rectangle feature kind."""
45         return "SketchRectangle"
46
47     @staticmethod
48     def START_ID():
49         """Returns ID of first corner."""
50         return "RectStartPoint"
51
52     @staticmethod
53     def END_ID():
54         """Returns ID of second corner."""
55         return "RectEndPoint"
56
57     @staticmethod
58     def AUXILIARY_ID():
59         """Returns whether the rectangle is accessory."""
60         return "Auxiliary"
61
62     @staticmethod
63     def LINES_LIST_ID():
64         """Returns ID of list containing lines created."""
65         return "RectangleList"
66
67     @staticmethod
68     def RECTANGLE_TYPE_ID():
69         """Returns ID of type of rectangle creation (by corners or by center and corner)."""
70         return "RectangleType"
71
72     @staticmethod
73     def RECTANGLE_BY_CORNERS_ID():
74         """Returns ID of creation type by opposite corners."""
75         return "RectangleTypeByCorners"
76
77     @staticmethod
78     def RECTANGLE_CENTERED_ID():
79         """Returns ID of creation type by center point and a corner."""
80         return "RectangleTypeCentered"
81
82     @staticmethod
83     def CENTER_ID():
84         """Returns ID of center point."""
85         return "RectCenterPoint"
86
87     @staticmethod
88     def CENTER_REF_ID():
89         """Returns ID of the reference to the center point."""
90         return "RectCenterPointRef"
91
92     @staticmethod
93     def CORNER_ID():
94         """Returns ID of a corner."""
95         return "RectCornerPoint"
96
97
98     def getKind(self):
99         """Override Feature.getKind()"""
100         return SketchPlugin_Rectangle.ID()
101
102
103 # Initialization of the rectangle
104
105     def initAttributes(self):
106         """Override Feature.initAttributes()"""
107         # Flag whether the rectangle is accessory
108         self.data().addAttribute(self.AUXILIARY_ID(), ModelAPI.ModelAPI_AttributeBoolean.typeId())
109         # Corners of the rectangle (being defined by opposite corners)
110         self.data().addAttribute(self.START_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId())
111         self.data().addAttribute(self.END_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId())
112         # List with both contour and diagonal lines
113         self.data().addAttribute(self.LINES_LIST_ID(), ModelAPI.ModelAPI_AttributeRefList.typeId())
114         ModelAPI.ModelAPI_Session.get().validators().registerNotObligatory(self.getKind(), self.LINES_LIST_ID())
115         # Type of rectangle
116         self.data().addAttribute(self.RECTANGLE_TYPE_ID(), ModelAPI.ModelAPI_AttributeString.typeId())
117         # Center and corner of centered rectangle (being defined by center and corner)
118         self.data().addAttribute(self.CENTER_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId())
119         self.data().addAttribute(self.CORNER_ID(), GeomDataAPI.GeomDataAPI_Point2D.typeId())
120
121         self.data().addAttribute(self.CENTER_REF_ID(), ModelAPI.ModelAPI_AttributeRefAttr.typeId())
122         ModelAPI.ModelAPI_Session.get().validators().registerNotObligatory(self.getKind(), self.CENTER_REF_ID())
123
124     def isMacro(self):
125         """
126         Override Feature.isMacro().
127         Rectangle feature is macro: removes itself on the creation transaction finish.
128         """
129         return True
130
131
132 # Editing of a rectangle. It is called on select of the first generative point
133 # and on hover of the second generative point. Generative point is either corner or center,
134 # depending on rectangle type. And also it is called on call of Sketch.addRectangleCentered(...) in TUI.
135
136     def execute(self):
137         # Retrieve list of already created lines
138         aLinesList = self.reflist(self.LINES_LIST_ID())        
139         if aLinesList.size() == 1:
140             # Create remaining rectangle contour lines
141             for i in range (1, 4):
142                 aLine = self.__sketch.addFeature("SketchLine")
143                 aLinesList.append(aLine)
144             self.__updateLines()
145
146             # Connect rectangle contour lines
147             aStartPoints = []
148             for i in range (0, 4):
149                 aLine = ModelAPI.objectToFeature(aLinesList.object(i))
150                 iPrev = i - 1 if i != 0 else 3
151                 aPrevLine = ModelAPI.objectToFeature(aLinesList.object(iPrev))
152                 aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence")
153                 aCoincidence.refattr("ConstraintEntityA").setAttr(aPrevLine.attribute("EndPoint"))
154                 aCoincidence.refattr("ConstraintEntityB").setAttr(aLine.attribute("StartPoint"))
155                 aStartPoints.append(aLine.attribute("StartPoint"))
156
157             # Update coordinates of created lines
158             self.__updateLines()
159
160             # Flags, indicating perpendicular constraint is imposed on contour lines i and i+1.
161             self.__isPERP = [False, False, False]
162
163             # Create auxiliary diagonals in case of centered rectangle
164             if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID():
165                 aDiag1 = self.__sketch.addFeature("SketchLine")
166                 aLinesList.append(aDiag1)
167                 aDiag2 = self.__sketch.addFeature("SketchLine")
168                 aLinesList.append(aDiag2)
169
170                 # Add coincidences between diagonals' endpoints and rectangle vertices
171                 aPoints = [aDiag1.attribute("StartPoint"), aDiag2.attribute("StartPoint"),
172                            aDiag1.attribute("EndPoint"), aDiag2.attribute("EndPoint")]
173                 
174                 for i in range (0, len(aPoints)):
175                     aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence")
176                     aCoincidence.refattr("ConstraintEntityA").setAttr(aStartPoints[i])
177                     aCoincidence.refattr("ConstraintEntityB").setAttr(aPoints[i])
178
179                 # Update coordinates of created lines
180                 self.__updateLines()
181                 aDiag1.execute()
182                 aDiag2.execute()
183
184                 # coincidences between center point and diagonals
185                 attr = self.__getPoint2DAttrOfSketchPoint(self.refattr(self.CENTER_REF_ID()))
186                 if attr is not None:
187                     for line in [aDiag1.lastResult(), aDiag2.lastResult()]:
188                         aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence")
189                         aCoincidence.refattr("ConstraintEntityA").setAttr(attr)
190                         aCoincidence.refattr("ConstraintEntityB").setObject(line)
191
192         # Add perpendicular constraints to the contour lines, which already have result
193         for i in range (0, 3):
194             if self.__isPERP[i]:
195                 continue
196             
197             aLine_A = ModelAPI.objectToFeature(aLinesList.object(i))
198             aLineResult_A = aLine_A.lastResult()
199             if aLineResult_A is None:
200                 continue
201
202             aLine_B = ModelAPI.objectToFeature(aLinesList.object(i+1))
203             aLineResult_B = aLine_B.lastResult()
204             if aLineResult_B is None:
205                 continue
206
207             aConstraintPerp = self.__sketch.addFeature("SketchConstraintPerpendicular")
208             aConstraintPerp.refattr("ConstraintEntityA").setObject(aLine_A.lastResult())
209             aConstraintPerp.refattr("ConstraintEntityB").setObject(aLine_B.lastResult())
210             self.__isPERP[i] = True
211
212
213     def attributeChanged(self, theID):
214         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():
215             # Find the sketch containing this rectangle
216             self.__sketch = None
217             aRefsToMe = self.data().refsToMe()
218             for aRefToMe in aRefsToMe:
219                 aFeature = ModelAPI.objectToFeature(aRefToMe.owner())
220                 if aFeature.getKind() == "Sketch":
221                     self.__sketch = ModelAPI.featureToCompositeFeature(aFeature)
222                     break
223
224             if theID == self.CENTER_ID():
225                 aCenter = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CENTER_ID())) # shared_ptr to Point2D
226                 aCenterSketchPointAttr = self.refattr(self.CENTER_REF_ID())
227                 if (not aCenterSketchPointAttr.isInitialized()):
228                     # Create SketchPoint. In .execute() it will be constrained to keep center.
229                     aCenterSketchPoint = self.__sketch.addFeature("SketchPoint")
230                     aCenterSketchPoint.data().boolean("Auxiliary").setValue(True)
231                     aCenterSketchPointCoords = GeomDataAPI.geomDataAPI_Point2D(aCenterSketchPoint.attribute("PointCoordinates")) # shared_ptr to Point2D                    
232                     aCenterSketchPointCoords.setValue(aCenter.x(), aCenter.y())
233                     aCenterSketchPointAttr.setObject(aCenterSketchPoint)
234                 else:
235                     # Maintain consistency between center SketchPoint and center Point2D.
236                     aCenterSketchPointCoordsAttr = self.__getPoint2DAttrOfSketchPoint(aCenterSketchPointAttr)
237                     if (aCenterSketchPointCoordsAttr == None):
238                         Warning("Faulty logic")
239                     else:
240                         aCenterSketchPointCoords = GeomDataAPI.geomDataAPI_Point2D(aCenterSketchPointCoordsAttr) # shared_ptr to Point2D
241                         aCenterSketchPointCoords.setValue(aCenter.x(), aCenter.y())
242             elif theID == self.CENTER_REF_ID():
243                 aCenterSketchPointAttr = self.refattr(self.CENTER_REF_ID())
244                 aCenterSketchPointCoordsAttr = self.__getPoint2DAttrOfSketchPoint(aCenterSketchPointAttr) # shared_ptr to Point2D
245                 if (aCenterSketchPointCoordsAttr == None):
246                     Warning("Faulty logic. Attempt to set rectangle's attribute " + self.CENTER_REF_ID() + " not with refattr to SketchPoint.")
247                     return
248                 
249                 # Maintain consistency between center SketchPoint and center Point2D.
250                 aCenterSketchPointCoords = GeomDataAPI.geomDataAPI_Point2D(aCenterSketchPointCoordsAttr) # shared_ptr to Point2D
251                 aCenter = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CENTER_ID())) # shared_ptr to Point2D
252                 aCenter.setValue(aCenterSketchPointCoords.x(), aCenterSketchPointCoords.y())
253
254             aLinesList = self.reflist(self.LINES_LIST_ID())
255             aNbLines = aLinesList.size()
256             if aNbLines == 0:
257                 # If only one generative point is iniialized,
258                 # do not create the full set of contour lines to not clutter
259                 # UI with icons of constraints near the mouse pointer.
260                 aLine = self.__sketch.addFeature("SketchLine")
261                 aLinesList.append(aLine)                    
262
263             aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID()))
264             aEndPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.END_ID()))
265             aCenter = self.__getPoint2D(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID()))
266             aCorner = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CORNER_ID()))
267
268             if (aStartPoint.isInitialized() and aEndPoint.isInitialized()) or (aCenter is not None and aCorner.isInitialized()):
269               self.__updateLines()
270             else:
271               self.__updateLinesWithOnlyGenerativePoint()
272         elif theID == self.AUXILIARY_ID():
273             # Change aux attribute of contour lines
274             anAuxiliary = self.data().boolean(self.AUXILIARY_ID()).value()
275             aLinesList = self.reflist(self.LINES_LIST_ID())            
276             for i in range (0, min(aLinesList.size(), 4)):
277                 aLine = ModelAPI.objectToFeature(aLinesList.object(i))
278                 aLine.data().boolean("Auxiliary").setValue(anAuxiliary)
279         elif theID == self.RECTANGLE_TYPE_ID():
280             # TODO Find a way to distinguish "attribute changed" events on hover and on click.
281             # Now, if both generative points are selected, but the rectangle is not applied,
282             # and then rectangle type is changed, the unapplied rectangle is erased. 
283             # It should be applied instead.
284             aLinesList = self.reflist(self.LINES_LIST_ID()).clear()
285             aCenterSketchPointAttr = self.refattr(self.CENTER_REF_ID())
286             aCenterSketchPointAttr.reset()
287             self.attribute(self.START_ID()).reset()
288             self.attribute(self.END_ID()).reset()
289             self.attribute(self.CENTER_ID()).reset()
290             self.attribute(self.CORNER_ID()).reset()
291
292
293     def __getPoint2DAttrOfSketchPoint(self, theSketchPointRefAttr):
294         if theSketchPointRefAttr.isObject() and theSketchPointRefAttr.object() is not None:
295             feature = ModelAPI.ModelAPI_Feature.feature(theSketchPointRefAttr.object())
296             if feature.getKind() == "SketchPoint":
297                 return feature.attribute("PointCoordinates")
298         else:
299             return theSketchPointRefAttr.attr()
300
301
302     def __getPoint2D(self, thePoint2DAttr, theSketchPointRefAttr):
303         attr = thePoint2DAttr
304         if theSketchPointRefAttr.isInitialized():
305             aPoint2DAttr = self.__getPoint2DAttrOfSketchPoint(theSketchPointRefAttr)
306             if aPoint2DAttr is not None:
307                 attr = aPoint2DAttr
308         if attr is None or not attr.isInitialized():
309             return None
310         return GeomDataAPI.geomDataAPI_Point2D(attr).pnt()
311
312
313     def __updateLines(self):
314         if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID():
315             aCenter = self.__getPoint2D(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID()))
316             aCorner = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CORNER_ID()))
317             aStartX = 2.0 * aCenter.x() - aCorner.x()
318             aStartY = 2.0 * aCenter.y() - aCorner.y()
319             aX = [aStartX, aStartX, aCorner.x(), aCorner.x()]
320             aY = [aStartY, aCorner.y(), aCorner.y(), aStartY]
321         else:
322             aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID()))
323             aEndPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.END_ID()))
324             aX = [aStartPoint.x(), aStartPoint.x(), aEndPoint.x(), aEndPoint.x()]
325             aY = [aStartPoint.y(), aEndPoint.y(), aEndPoint.y(), aStartPoint.y()]        
326
327         # Retrieve list of already created lines
328         aLinesList = self.reflist(self.LINES_LIST_ID())
329         aNumOfContourLines = min(aLinesList.size(), 4)
330
331         # Do not update lines during update of coordinates
332         wasBlocked = []
333         for i in range (0, aLinesList.size()):
334             wasBlocked.append(aLinesList.object(i).data().blockSendAttributeUpdated(True))
335
336         # Update coordinates of rectangle lines
337         anAuxiliary = self.data().boolean(self.AUXILIARY_ID()).value()
338         for i in range (0, aNumOfContourLines):
339             aLine = ModelAPI.objectToFeature(aLinesList.object(i))
340             aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("StartPoint"))
341             aLineEnd = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint"))
342             aLineStart.setValue(aX[i-1], aY[i-1])
343             aLineEnd.setValue(aX[i], aY[i])
344             aLine.data().boolean("Auxiliary").setValue(anAuxiliary)
345
346         # Update auxiliary diagonals
347         if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID():
348             for i in range (aNumOfContourLines, aLinesList.size()):
349                 aLine = ModelAPI.objectToFeature(aLinesList.object(i))
350                 aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("StartPoint"))
351                 aLineEnd = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint"))
352                 aLineStart.setValue(aX[i-aNumOfContourLines-1], aY[i-aNumOfContourLines-1])
353                 aLineEnd.setValue(aX[i-aNumOfContourLines+1], aY[i-aNumOfContourLines+1])
354                 aLine.data().boolean("Auxiliary").setValue(True)
355
356         # Update the rectangle
357         for i in range (0, aLinesList.size()):
358             aLinesList.object(i).data().blockSendAttributeUpdated(wasBlocked[i], True)
359
360
361     def __updateLinesWithOnlyGenerativePoint(self):
362         # Retrieve list of already created lines
363         aLinesList = self.reflist(self.LINES_LIST_ID())
364         aNbLines = aLinesList.size()
365
366         aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID()))
367         if aStartPoint.isInitialized:
368             aX = aStartPoint.x()
369             aY = aStartPoint.y()
370         else:
371             aCenter = self.__getPoint2D(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID()))
372             aX = aCenter.x()
373             aY = aCenter.y()
374
375         # Update coordinates of rectangle lines
376         for i in range (0, aNbLines):
377             aLine = ModelAPI.objectToFeature(aLinesList.object(i))
378             aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint"))
379             aLineStart.setValue(aX, aY)