Salome HOME
7ad629f895e2be30deb929d77a7d57e62123bd5e
[modules/shaper.git] / src / PythonAddons / macros / rectangle / feature.py
1 # Copyright (C) 2014-2021  CEA/DEN, EDF R&D
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         # Creating corners of the rectangle
110         self.data().addAttribute(self.START_ID(), GeomDataAPI.GeomDataAPI_Point2D_typeId())
111         self.data().addAttribute(self.END_ID(), GeomDataAPI.GeomDataAPI_Point2D_typeId())
112         # Creating list to store 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 the rectangle
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 # Edition of the rectangle
132
133     def execute(self):
134         # Retrieving list of already created lines
135         aLinesList = self.reflist(self.LINES_LIST_ID())
136         aNbLines = aLinesList.size()
137         if aNbLines == 1:
138             # Create 1-4 lines to compose the rectangle
139             for i in range (0, 3):
140                 aLine = self.__sketch.addFeature("SketchLine")
141                 aLinesList.append(aLine)
142             self.updateLines()
143             aNbLines = aLinesList.size()
144             aStartPoints = []
145             # Create constraints to keep the rectangle
146             for i in range (0, aNbLines):
147                 aLine = ModelAPI.objectToFeature(aLinesList.object(i))
148                 # connect neighbor lines by coincidence
149                 iPrev = i - 1
150                 if iPrev < 0:
151                     iPrev = aNbLines - 1
152                 aPrevLine = ModelAPI.objectToFeature(aLinesList.object(iPrev))
153                 aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence")
154                 aRefAttrA = aCoincidence.refattr("ConstraintEntityA")
155                 aRefAttrB = aCoincidence.refattr("ConstraintEntityB")
156                 aRefAttrA.setAttr(aPrevLine.attribute("EndPoint"))
157                 aRefAttrB.setAttr(aLine.attribute("StartPoint"))
158                 aStartPoints.append(aLine.attribute("StartPoint"))
159             # Flags which show horizontal or vertical constraint is build for correponding line
160             self.__isHV = [False, False, False, False]
161             # Update coordinates of created lines
162             self.updateLines()
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                 # coincidences in corners
170                 aPoints = [aDiag1.attribute("StartPoint"), aDiag2.attribute("StartPoint"),
171                            aDiag1.attribute("EndPoint"), aDiag2.attribute("EndPoint")]
172                 for i in range (0, len(aPoints)):
173                     aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence")
174                     aRefAttrA = aCoincidence.refattr("ConstraintEntityA")
175                     aRefAttrB = aCoincidence.refattr("ConstraintEntityB")
176                     aRefAttrA.setAttr(aStartPoints[i])
177                     aRefAttrB.setAttr(aPoints[i])
178                 # Update coordinates of created lines
179                 self.updateLines()
180                 aDiag1.execute()
181                 aDiag2.execute()
182                 # coincidences between center point and diagonals
183                 refPnt = self.getReferencePoint(self.refattr(self.CENTER_REF_ID()))
184                 if refPnt is not None:
185                     for line in [aDiag1.lastResult(), aDiag2.lastResult()]:
186                         aCoincidence = self.__sketch.addFeature("SketchConstraintCoincidence")
187                         aCoincidence.refattr("ConstraintEntityA").setAttr(refPnt)
188                         aCoincidence.refattr("ConstraintEntityB").setObject(line)
189         # Add horizontal and vertical constraint for the lines which already have result
190         for i in range (0, 4):
191             if self.__isHV[i]:
192                 continue
193             aLine = ModelAPI.objectToFeature(aLinesList.object(i))
194             aLineResult = aLine.lastResult()
195             if aLineResult is None:
196                 continue
197             aHVName = "SketchConstraintHorizontal"
198             if i % 2 == 1:
199                 aHVName = "SketchConstraintVertical"
200             aHVConstraint = self.__sketch.addFeature(aHVName)
201             aRefAttrA = aHVConstraint.refattr("ConstraintEntityA")
202             aRefAttrA.setObject(aLine.lastResult())
203             self.__isHV[i] = True
204
205     def attributeChanged(self, theID):
206         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():
207             # Search the sketch containing this rectangle
208             self.__sketch = None
209             aRefs = self.data().refsToMe();
210             for iter in aRefs:
211                 aFeature = ModelAPI.objectToFeature(iter.owner())
212                 if aFeature.getKind() == "Sketch":
213                     self.__sketch = ModelAPI.featureToCompositeFeature(aFeature)
214                     break
215
216             aLinesList = self.reflist(self.LINES_LIST_ID())
217             aNbLines = aLinesList.size()
218             if aNbLines == 0:
219                 # Create first line to be able to create a coincidence with selected point/feature
220                 for i in range (0, 1):
221                     aLine = self.__sketch.addFeature("SketchLine")
222                     aLinesList.append(aLine)
223
224             aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID()))
225             aEndPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.END_ID()))
226             aCenter = self.getPointByRef(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID()))
227             aCorner = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CORNER_ID()))
228             if (aStartPoint.isInitialized() and aEndPoint.isInitialized()) or (aCenter is not None and aCorner.isInitialized()):
229               self.updateLines()
230             else:
231               self.updateStartPoint()
232         if theID == self.AUXILIARY_ID():
233             anAuxiliary = self.data().boolean(self.AUXILIARY_ID()).value()
234             aLinesList = self.reflist(self.LINES_LIST_ID())
235             aNbLines = aLinesList.size()
236             # Update coordinates of rectangle lines
237             for i in range (0, aNbLines):
238                 aLine = ModelAPI.objectToFeature(aLinesList.object(i))
239                 aLine.data().boolean("Auxiliary").setValue(anAuxiliary)
240
241     def getReferencePoint(self, theRef):
242         if theRef.isObject() and theRef.object() is not None:
243             feature = ModelAPI.ModelAPI_Feature.feature(theRef.object())
244             if feature.getKind() == "SketchPoint":
245                 return feature.attribute("PointCoordinates")
246         else:
247             return theRef.attr()
248         return None
249
250     def getPointByRef(self, thePoint, theRef):
251         attr = thePoint
252         if theRef.isInitialized():
253             refPnt = self.getReferencePoint(theRef)
254             if refPnt is not None:
255                 attr = refPnt
256         if attr is None or not attr.isInitialized():
257             return None
258         return GeomDataAPI.geomDataAPI_Point2D(attr).pnt()
259
260     def updateLines(self):
261         # Retrieving list of already created lines
262         aLinesList = self.reflist(self.LINES_LIST_ID())
263         aNbLines = min(aLinesList.size(), 4)
264         if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID():
265             aCenter = self.getPointByRef(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID()))
266             aCorner = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.CORNER_ID()))
267             aStartX = 2.0 * aCenter.x() - aCorner.x()
268             aStartY = 2.0 * aCenter.y() - aCorner.y()
269             aX = [aStartX, aStartX, aCorner.x(), aCorner.x()]
270             aY = [aStartY, aCorner.y(), aCorner.y(), aStartY]
271         else:
272             aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID()))
273             aEndPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.END_ID()))
274             aX = [aStartPoint.x(), aStartPoint.x(), aEndPoint.x(), aEndPoint.x()]
275             aY = [aStartPoint.y(), aEndPoint.y(), aEndPoint.y(), aStartPoint.y()]
276         anAuxiliary = self.data().boolean(self.AUXILIARY_ID()).value()
277
278         # do not recalculate the rectrangle after each update
279         wasBlocked = []
280         for i in range (0, aLinesList.size()):
281             wasBlocked.append(aLinesList.object(i).data().blockSendAttributeUpdated(True))
282
283         # Update coordinates of rectangle lines
284         for i in range (0, aNbLines):
285             aLine = ModelAPI.objectToFeature(aLinesList.object(i))
286             aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("StartPoint"))
287             aLineEnd = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint"))
288             aLineStart.setValue(aX[i-1], aY[i-1])
289             aLineEnd.setValue(aX[i], aY[i])
290             aLine.data().boolean("Auxiliary").setValue(anAuxiliary)
291
292         # Update auxiliary diagonals
293         if self.string(self.RECTANGLE_TYPE_ID()).value() == self.RECTANGLE_CENTERED_ID():
294             for i in range (aNbLines, aLinesList.size()):
295                 aLine = ModelAPI.objectToFeature(aLinesList.object(i))
296                 aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("StartPoint"))
297                 aLineEnd = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint"))
298                 aLineStart.setValue(aX[i-aNbLines-1], aY[i-aNbLines-1])
299                 aLineEnd.setValue(aX[i-aNbLines+1], aY[i-aNbLines+1])
300                 aLine.data().boolean("Auxiliary").setValue(True)
301
302         # update the rectangle
303         for i in range (0, aLinesList.size()):
304             aLinesList.object(i).data().blockSendAttributeUpdated(wasBlocked[i], True)
305
306     def updateStartPoint(self):
307         # Retrieving list of already created lines
308         aLinesList = self.reflist(self.LINES_LIST_ID())
309         aNbLines = aLinesList.size()
310
311         aStartPoint = GeomDataAPI.geomDataAPI_Point2D(self.attribute(self.START_ID()))
312         if aStartPoint.isInitialized:
313             aX = aStartPoint.x()
314             aY = aStartPoint.y()
315         else:
316             aCenter = self.getPointByRef(self.attribute(self.CENTER_ID()), self.refattr(self.CENTER_REF_ID()))
317             aX = aCenter.x()
318             aY = aCenter.y()
319
320         # Update coordinates of rectangle lines
321         for i in range (0, aNbLines):
322             aLine = ModelAPI.objectToFeature(aLinesList.object(i))
323             aLineStart = GeomDataAPI.geomDataAPI_Point2D(aLine.attribute("EndPoint"))
324             aLineStart.setValue(aX, aY)