Salome HOME
Issue #1440 Crash when edit box with parameter: temporary undoes previous modificatio...
[modules/shaper.git] / src / PythonAPI / model / sketcher / sketch.py
1 # Author: Daniel Brunier-Coulin with contribution by Mikhail Ponikarov
2 #         finalized by Renaud Nedelec and Sergey Pokhodenko
3 # Copyright (C) 2014-20xx CEA/DEN, EDF R&D
4
5 """Sketcher interface.
6 This interface allows to add a sketch
7 in a part or partset.
8 The created sketch object provides all the needed methods
9 for sketch modification and constraint edition.
10
11 Example of code:
12
13 .. doctest::
14
15    >>> import model
16    >>> model.begin()
17    >>> partset = model.moduleDocument()
18    >>> part = model.addPart(partset).document()
19    >>> plane = model.defaultPlane("XOY")
20    >>> sketch = model.addSketch(part, plane)
21    >>> line = sketch.addLine(0, 0, 0, 1)
22    >>> line.endPoint().x()
23    0.0
24    >>> line.endPoint().y()
25    1.0
26 """
27
28 from ModelAPI import modelAPI_ResultConstruction, featureToCompositeFeature
29 from GeomDataAPI import geomDataAPI_Point, geomDataAPI_Dir
30 from GeomAlgoAPI import GeomAlgoAPI_SketchBuilder, ShapeList
31
32 from model.sketcher.point import Point
33 from model.sketcher.line import Line
34 from model.sketcher.circle import Circle
35 from model.sketcher.arc import Arc
36 from model.sketcher.mirror import Mirror
37 from model.roots import Interface
38 from model.tools import Selection
39
40
41 def addSketch(document, plane):
42     """Add a sketch to a Part or PartSet.
43
44     Arguments:
45        document(ModelAPI_Document): part or partset document
46        plane(geom.Ax3): plane on wich the sketch is built
47
48     Returns:
49        Sketch: sketch object
50     """
51     feature = featureToCompositeFeature(document.addFeature("Sketch"))
52     return Sketch(feature, plane)
53
54 class Sketch(Interface):
55     """Interface class for Sketch feature."""
56     def __init__(self, feature, *args):
57         """Initialize a 2D Sketch on the given plane.
58
59         The plane can be defined either by:
60         - a 3D axis system (geom.Ax3),
61         - an existing face identified by its topological name.
62         """
63         Interface.__init__(self, feature)
64         assert(self._feature.getKind() == "Sketch")
65
66         self._origin = geomDataAPI_Point(
67             self._feature.data().attribute("Origin")
68             )
69         self._dir_x = geomDataAPI_Dir(
70             self._feature.data().attribute("DirX")
71             )
72         self._norm = geomDataAPI_Dir(
73             self._feature.data().attribute("Norm")
74             )
75         self._external = self._feature.data().selection("External")
76
77         # If no arguments are given the attributes of the feature
78         # are not Initialized
79         if args is not None:
80             plane = args[0]
81             if isinstance(plane, str):
82                 self.__sketchOnFace(plane)
83             else:
84                 self.__sketchOnPlane(plane)
85
86     def __sketchOnPlane(self, plane):
87         """Create the sketch on a plane."""
88         origin = plane.location()
89         normal = plane.direction()
90         x_direction = plane.xDirection()
91         self._origin.setValue(origin.x(), origin.y(), origin.z())
92         self._norm.setValue(normal.x(), normal.y(), normal.z())
93         self._dir_x.setValue(x_direction.x(), x_direction.y(), x_direction.z())
94
95     def __sketchOnFace(self, name):
96         """Initialize the sketch on a face given by its name."""
97         self._external.selectSubShape("FACE", name)
98
99     #-------------------------------------------------------------
100     #
101     # Creation of Geometries
102     #
103     #-------------------------------------------------------------
104
105     def addPoint(self, *args):
106         """Add a point to the sketch."""
107         if not args:
108             raise TypeError("No arguments given")
109         point_feature = self._feature.addFeature("SketchPoint")
110         return Point(point_feature, *args)
111
112     def addLine(self, *args):
113         """Add a line to the sketch.
114
115         .. function:: addLine(name)
116         Select an existing line. The line is added to the sketch with a rigid
117         constraint (it cannot be modified by the sketch)
118
119         Arguments:
120             name(str): name of an existing line
121
122         .. function:: addLine(start, end)
123         Create a line by points
124
125         Arguments:
126            start(point): start point of the line
127            end(point): end point of the line
128
129         .. function:: addLine(start_x, start_y, end_x, end_y)
130         Create a line by coordinates
131
132         Arguments:
133            start_x(double): start point x coordinate
134         """
135         if not args:
136             raise TypeError("No arguments given")
137         line_feature = self._feature.addFeature("SketchLine")
138         line_interface = Line(line_feature, *args)
139         # if the line is created by name add a rigid constraint
140         # to the created line
141         if len(args) == 1 and isinstance(args[0], str):
142             constraint = self._feature.addFeature("SketchConstraintRigid")
143             constraint.refattr("ConstraintEntityA").setObject(
144                 line_feature.firstResult()
145                 )
146         return line_interface
147
148     def addCircle(self, *args):
149         """Add a circle to this Sketch."""
150         if not args:
151             raise TypeError("No arguments given")
152         circle_feature = self._feature.addFeature("SketchCircle")
153         return Circle(circle_feature, *args)
154
155     def addArc(self, *args, **kwargs):
156         """Add an arc of circle to the sketch and return an arc object.
157
158         Two different syntaxes are allowed:
159
160         .. function:: addArc(center, start, end)
161
162         Arguments:
163             center (point): center of the arc
164             start (point): start point of the arc
165             end (point): end point of the arc
166
167         .. function:: addArc(center_x, center_y, start_x, start_y, end_x, end_y)
168
169         Same as above but with coordinates
170
171         Returns:
172             Arc: arc object
173         Raises:
174             TypeError: if no argument is provided
175         """
176         if not args:
177             raise TypeError("No arguments given")
178         arc_feature = self._feature.addFeature("SketchArc")
179         return Arc(arc_feature, *args, **kwargs)
180
181     #-------------------------------------------------------------
182     #
183     # Creation of Geometrical and Dimensional Constraints
184     #
185     #-------------------------------------------------------------
186
187     def setCoincident(self, p1, p2):
188         """Set coincident the two given points and add the corresponding
189         constraint to this Sketch."""
190         # assert(p1 and p2) NOTE : if an argument is missing python
191         # will raise TypeError by itself.
192         # It seems better to check only that provided arguments are not
193         # None
194         if p1 is None or p2 is None:
195             raise TypeError("NoneType argument given")
196         constraint = self._feature.addFeature("SketchConstraintCoincidence")
197         self._fillAttribute(constraint.refattr("ConstraintEntityA"), p1)
198         self._fillAttribute(constraint.refattr("ConstraintEntityB"), p2)
199         self.execute()
200         return constraint
201
202     def setParallel(self, l1, l2):
203         """Set parallel the two given lines and add the corresponding
204         constraint to this Sketch."""
205         if l1 is None or l2 is None:
206             raise TypeError("NoneType argument given")
207         constraint = self._feature.addFeature("SketchConstraintParallel")
208         constraint.data().refattr("ConstraintEntityA").setObject(l1)
209         constraint.data().refattr("ConstraintEntityB").setObject(l2)
210         self.execute()
211         return constraint
212
213     def setPerpendicular(self, l1, l2):
214         """Set perpendicular the two given lines and add the corresponding
215         constraint to this Sketch."""
216         if l1 is None or l2 is None:
217             raise TypeError("NoneType argument given")
218         constraint = self._feature.addFeature("SketchConstraintPerpendicular")
219         constraint.data().refattr("ConstraintEntityA").setObject(l1)
220         constraint.data().refattr("ConstraintEntityB").setObject(l2)
221         self.execute()
222         return constraint
223
224     def setHorizontal(self, line):
225         """Set horizontal the given line and add the corresponding
226         constraint to this Sketch."""
227         if line is None:
228             raise TypeError("NoneType argument given")
229         constraint = self._feature.addFeature("SketchConstraintHorizontal")
230         constraint.data().refattr("ConstraintEntityA").setObject(line)
231         self.execute()
232         return constraint
233
234     def setVertical(self, line):
235         """Set vertical the given line and add the corresponding
236         constraint to this Sketch."""
237         if line is None:
238             raise TypeError("NoneType argument given")
239         constraint = self._feature.addFeature("SketchConstraintVertical")
240         constraint.data().refattr("ConstraintEntityA").setObject(line)
241         self.execute()
242         return constraint
243
244     def setDistance(self, point, line, length):
245         """Set the distance between the given point and line, and add
246         the corresponding constraint to this Sketch."""
247         if point is None or line is None:
248             raise TypeError("NoneType argument given")
249         constraint = self._feature.addFeature("SketchConstraintDistance")
250         if isinstance(line, basestring):
251             # Add the edge identified by the given topological name
252             # to this Sketch
253             line = self.addLine(line).result()
254         constraint.data().refattr("ConstraintEntityA").setAttr(point)
255         constraint.data().refattr("ConstraintEntityB").setObject(line)
256         constraint.data().real("ConstraintValue").setValue(length)
257         self.execute()
258         return constraint
259
260     def setLength(self, line, length):
261         """Set the length of the given line and add the corresponding
262         constraint to this Sketch."""
263         if line is None:
264             raise TypeError("NoneType argument given")
265         constraint = self._feature.addFeature("SketchConstraintLength")
266         constraint.data().refattr("ConstraintEntityA").setObject(line)
267         self._fillAttribute(constraint.real("ConstraintValue"), length)
268         self.execute()
269         return constraint
270
271     def setRadius(self, circle, radius):
272         """Set the radius of the given circle and add the corresponding
273         constraint to this Sketch."""
274         constraint = self._feature.addFeature("SketchConstraintRadius")
275         self._fillAttribute(constraint.refattr("ConstraintEntityA"), circle)
276         self._fillAttribute(constraint.real("ConstraintValue"), radius)
277         self.execute()
278         return constraint
279
280     def setEqual(self, object_1, object_2):
281         """Set the radii of two circles or the length of two lines equal.
282
283         The corresponding constraint is added to the sketch"""
284         constraint = self._feature.addFeature("SketchConstraintEqual")
285         constraint.data().refattr("ConstraintEntityA").setObject(object_1)
286         constraint.data().refattr("ConstraintEntityB").setObject(object_2)
287         self.execute()
288         return constraint
289
290     def setAngle(self, line_1, line_2, angle):
291         """Set the angle between the given 2 lines and add the corresponding
292         constraint to the sketch."""
293         constraint = self._feature.addFeature("SketchConstraintAngle")
294         constraint.data().refattr("ConstraintEntityA").setObject(line_1)
295         constraint.data().refattr("ConstraintEntityB").setObject(line_2)
296         constraint.data().real("ConstraintValue").setValue(angle)
297         self.execute()
298         return constraint
299
300     def setTangent(self, object_1, object_2):
301         """Set a tangential continuity between two objects
302         at their coincidence point."""
303         if object_1 is None or object_2 is None:
304             raise TypeError("NoneType argument given")
305         constraint = self._feature.addFeature("SketchConstraintTangent")
306         constraint.data().refattr("ConstraintEntityA").setObject(object_1)
307         constraint.data().refattr("ConstraintEntityB").setObject(object_2)
308         self.execute()
309         return constraint
310
311     def setFillet(self, *args):
312         """Set a fillet constraint between the 2 given lines with the given
313         filleting radius."""
314         assert(args)
315         constraint = self._feature.addFeature("SketchConstraintFillet")
316         if len(args) == 3:
317             line_1, line_2, radius = args
318             constraint.data().refattr("ConstraintEntityA").setObject(line_1)
319             constraint.data().reflist("ConstraintEntityB").clear()
320             constraint.data().reflist("ConstraintEntityB").append(line_2)
321         elif len(args) == 2:
322             point, radius = args
323             self._fillAttribute(constraint.data().refattrlist("ConstraintEntityA"), [point])
324             self._fillAttribute(constraint.real("ConstraintValue"), radius)
325         self.execute()
326         return constraint
327
328     def setRigid(self, object_):
329         """Set a rigid constraint on a given object."""
330         constraint = self._feature.addFeature("SketchConstraintRigid")
331         self._fillAttribute(constraint.refattr("ConstraintEntityA"), object_)
332         self.execute()
333         return constraint
334
335     #-------------------------------------------------------------
336     #
337     # Transformation constraints
338     #
339     #-------------------------------------------------------------
340
341     def addMirror(self, mirror_line, sketch_objects):
342         """Add a mirror transformation of the given objects to the sketch.
343
344         This transformation is a constraint.
345
346         :return: interface to the constraint
347         :rtype: Mirror object
348         """
349         mirror_constraint = self._feature.addFeature("SketchConstraintMirror")
350         mirror_interface = Mirror(mirror_constraint, mirror_line, sketch_objects)
351         self.execute()
352         return mirror_interface
353
354
355     #-------------------------------------------------------------
356     #
357     # Edition of Dimensional Constraints
358     #
359     #-------------------------------------------------------------
360
361     def setValue(self, constraint, value):
362         """Modify the value of the given dimensional constraint."""
363         constraint.data().real("ConstraintValue").setValue(value)
364
365     #-------------------------------------------------------------
366     #
367     # Macro functions combining geometry creation and constraints
368     #
369     #-------------------------------------------------------------
370
371     def addPolyline(self, *coords):
372         """Add a poly-line to this Sketch.
373
374         The end of consecutive segments are defined as coincident.
375         """
376         c0 = coords[0]
377         c1 = coords[1]
378         polyline = []
379         line_1 = self.addLine(c0, c1)
380         polyline.append(line_1)
381         # Adding and connecting next lines
382         for c2 in coords[2:]:
383             line_2 = self.addLine(c1, c2)
384             self.setCoincident(line_1.endPoint(), line_2.startPoint())
385             polyline.append(line_2)
386             c1 = c2
387             line_1 = line_2
388         return polyline
389
390     def addPolygon(self, *coords):
391         """Add a polygon to this Sketch.
392
393         The end of consecutive segments are defined as coincident.
394         """
395         pg = self.addPolyline(*coords)
396         # Closing the poly-line supposed being defined by at least 3 points
397         c0 = coords[0]
398         cn = coords[len(coords) - 1]
399         ln = self.addLine(cn, c0)
400         self.setCoincident(
401             pg[len(coords) - 2].endPoint(), ln.startPoint()
402             )
403         self.setCoincident(
404             ln.endPoint(), pg[0].startPoint()
405             )
406         pg.append(ln)
407         return pg
408
409     #-------------------------------------------------------------
410     #
411     # Getters
412     #
413     #-------------------------------------------------------------
414
415     def selectFace(self, *args):
416         """Select the geometrical entities of this Sketch on which
417         the result Face must be built.
418
419         When no entity is given, the face is based on all existing
420         geometry of this Sketch.
421         """
422         if len(args) == 0:
423             wire = modelAPI_ResultConstruction(
424                 self._feature.firstResult()
425                 ).shape()
426         elif len(args) == 1:
427             wire = args[0].shape()
428         else:
429             raise Exception("not yet implemented")
430         # TODO: simple version now, should be a list of selected faces
431         return [Selection(self.result(), self.buildShape(wire))]
432
433     def buildShape(self, wire):
434         """Build the result Shape of this Sketch according to the
435         selected geometrical entities."""
436         o = self._origin.pnt()
437         dx = self._dir_x.dir()
438         n = self._norm.dir()
439
440         # The faces are kept otherwise they are destroyed at exit
441         faces = ShapeList()
442         GeomAlgoAPI_SketchBuilder.createFaces(o, dx, n, wire, faces)
443         # TODO: Deal with several faces
444         return faces[0]
445
446     def result(self):
447         """Returns the result data of this Feature."""
448         return self._feature.firstResult()