Salome HOME
Fix bug in structural elements (PAL #2012)
[modules/geom.git] / src / GEOM_PY / structelem / parts.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2007-2011  CEA/DEN, EDF R&D, OPEN CASCADE
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21 """
22 This module defines the different structural element parts. It is used to
23 build the geometric shapes of the structural elements. It should not be used
24 directly in the general case. Structural elements should be created by the
25 class :class:`~salome.geom.structelem.StructuralElementManager`.
26 """
27
28 import salome
29
30 from salome.kernel.logger import Logger
31 from salome.kernel import termcolor
32 logger = Logger("salome.geom.structelem.parts", color = termcolor.RED)
33 from salome.geom.geomtools import getGeompy
34
35 import orientation
36
37 # Filling for the beams
38 FULL = "FULL"
39 HOLLOW = "HOLLOW"
40
41 # Minimum dimension for the shapes to extrude
42 MIN_DIM_FOR_EXTRUDED_SHAPE = 2e-4
43 MIN_LENGTH_FOR_EXTRUSION = 1e-4
44 MIN_THICKNESS = 1e-5
45
46
47 class InvalidParameterError(Exception):
48     """
49     This exception is raised when an invalid parameter is used to build a
50     structural element part.
51     """
52     
53     def __init__(self, groupName, expression, minValue, value):
54         self.groupName = groupName
55         self.expression = expression
56         self.minValue = minValue
57         self.value = value
58         
59     def __str__(self):
60         return "%s < %g (%s = %g in %s)" % (self.expression, self.minValue,
61                                             self.expression, self.value,
62                                             self.groupName)
63
64
65 class SubShapeID:
66     """
67     This class enables the use of subshapes in sets or as dictionary keys.
68     It implements __eq__ and __hash__ methods so that subshapes with the same
69     CORBA object `mainShape` and the same `id` are considered equal.
70     """
71
72     def __init__(self, mainShape, id):
73         self._mainShape = mainShape
74         self._id = id
75
76     def getObj(self, geom):
77         """
78         Return the subshape (GEOM object). `geom` is a pseudo-geompy object
79         used to find the geometrical object.
80         """
81         return geom.GetSubShape(self._mainShape, [self._id])
82     
83     def __eq__(self, other):
84         return self._mainShape._is_equivalent(other._mainShape) and \
85                self._id == other._id
86     
87     def __hash__(self):
88         return self._mainShape._hash(2147483647) ^ self._id
89
90
91 class StructuralElementPart:
92     """
93     This class is the base class for all structural element parts. It should
94     not be instantiated directly (consider it as an "abstract" class).
95
96     :type  studyId: integer
97     :param studyId: the ID of the study in which the part is created.
98
99     :type  groupName: string
100     :param groupName: the name of the underlying geometrical primitive in the
101                       study.
102
103     :type  groupGeomObj: GEOM object
104     :param groupGeomObj: the underlying geometrical primitive.
105
106     :type  parameters: dictionary
107     :param parameters: parameters defining the structural element (see
108                        subclasses for details).
109
110     :type  name: string
111     :param name: name to use for the created object in the study.
112
113     """
114     
115     DEFAULT_NAME = "StructElemPart"
116
117     def __init__(self, studyId, groupName, groupGeomObj, parameters,
118                  name = DEFAULT_NAME):
119         self._parameters = parameters
120         self.groupName = groupName
121         self._groupGeomObj = groupGeomObj
122         self._orientation = None
123         self._paramUserName = {}
124         self.name = name
125         self.geom = getGeompy(studyId)
126         self.baseShapesSet = set()
127         mainShape = self.geom.GetMainShape(groupGeomObj)
128         listIDs = self.geom.GetObjectIDs(groupGeomObj)
129         if mainShape is not None and listIDs is not None:
130             for id in listIDs:
131                 self.baseShapesSet.add(SubShapeID(mainShape, id))
132
133     def _getParameter(self, nameList, default = None):
134         """
135         This method finds the value of a parameter in the parameters
136         dictionary. The argument is a list because some parameters can have
137         several different names.
138         """
139         if len(nameList) > 0:
140             paramName = nameList[0]
141         for name in nameList:
142             if self._parameters.has_key(name):
143                 self._paramUserName[paramName] = name
144                 return self._parameters[name]
145         return default
146
147     def _getParamUserName(self, paramName):
148         """
149         This method finds the user name for a parameter.
150         """
151         if self._paramUserName.has_key(paramName):
152             return self._paramUserName[paramName]
153         else:
154             return paramName
155
156     def __repr__(self):
157         reprdict = self.__dict__.copy()
158         del reprdict["_parameters"]
159         del reprdict["groupName"]
160         del reprdict["_groupGeomObj"]
161         del reprdict["_paramUserName"]
162         del reprdict["name"]
163         del reprdict["geom"]
164         del reprdict["baseShapesSet"]
165         return '%s("%s", %s)' % (self.__class__.__name__, self.groupName,
166                                  reprdict)
167
168     def addOrientation(self, orientParams):
169         """
170         Add orientation information to the structural element part. See class
171         :class:`~salome.geom.structelem.orientation.Orientation1D` for the description
172         of the parameters.
173         """
174         self._orientation.addParams(orientParams)
175
176     def _checkSize(self, value, mindim, expression):
177         """
178         This method checks that some parameters or some expressions involving
179         those parameters are greater than a minimum value.
180         """
181         if value < mindim:
182             raise InvalidParameterError(self.groupName, expression,
183                                         mindim, value)
184
185     def build(self):
186         """
187         Build the geometric shapes and the markers corresponding to the
188         structural element part in the study `studyId`.
189         """
190         shape = self._buildPart()
191         markers = self._buildMarkers()
192         shape.SetColor(self._groupGeomObj.GetColor())
193         for marker in markers:
194             marker.SetColor(self._groupGeomObj.GetColor())
195         return (shape, markers)
196
197     def _buildPart(self):
198         """
199         This abstract method must be implemented in subclasses and should
200         create the geometrical shape(s) of the structural element part.
201         """
202         raise NotImplementedError("Method _buildPart not implemented in class"
203                                   " %s (it must be implemented in "
204                                   "StructuralElementPart subclasses)." %
205                                   self.__class__.__name__)
206
207     def _buildMarkers(self):
208         """
209         This abstract method must be implemented in subclasses and should
210         create the markers defining the orientation of the structural element
211         part.
212         """
213         raise NotImplementedError("Method _buildMarker not implemented in "
214                                   "class %s (it must be implemented in "
215                                   "StructuralElementPart subclasses)." %
216                                   self.__class__.__name__)
217
218     def _getSubShapes(self, minDim = MIN_LENGTH_FOR_EXTRUSION):
219         """
220         Find and return the base subshapes in the structural element part.
221         """
222         subShapes = []
223         for subShapeID in self.baseShapesSet:
224             subShape = subShapeID.getObj(self.geom)
225             length = self.geom.BasicProperties(subShape)[0]
226             if length < minDim:
227                 logger.warning("Length too short (%s - ID %s, length = %g), "
228                                "subshape will not be used in structural "
229                                "element" % (self.groupName, subShapeID._id,
230                                             length))
231             else:
232                 subShapes.append(subShape)
233         return subShapes
234
235
236 class Beam(StructuralElementPart):
237     """
238     This class is an "abstract" class for all 1D structural element parts. It
239     should not be instantiated directly. See class
240     :class:`StructuralElementPart` for the description of the parameters.
241     """
242
243     DEFAULT_NAME = "Beam"
244
245     def __init__(self, studyId, groupName, groupGeomObj, parameters,
246                  name = DEFAULT_NAME):
247         StructuralElementPart.__init__(self, studyId, groupName, groupGeomObj,
248                                        parameters, name)
249         self._orientation = orientation.Orientation1D()
250
251     def _isReversed(self, path):
252         """
253         This method checks if a 1D object is "reversed", i.e. if its
254         orientation is different than the orientation of the underlying OCC
255         object.
256         """
257         length = self.geom.BasicProperties(path)[0]
258         p1 = self.geom.MakeVertexOnCurve(path, 0.0)
259         p2 = self.geom.GetFirstVertex(path)
260         dist = self.geom.MinDistance(p1, p2)
261         return dist > length / 2
262
263     def _getVertexAndTangentOnOrientedWire(self, path, param):
264         """
265         Get a vertex and the corresponding tangent on a wire by parameter.
266         This method takes into account the "real" orientation of the wire
267         (i.e. the orientation of the underlying OCC object).
268         """
269         if self._isReversed(path):
270             vertex = self.geom.MakeVertexOnCurve(path, 1.0 - param)
271             invtangent = self.geom.MakeTangentOnCurve(path, 1.0 - param)
272             tanpoint = self.geom.MakeTranslationVectorDistance(vertex,
273                                                                invtangent,
274                                                                -1.0)
275             tangent = self.geom.MakeVector(vertex, tanpoint)
276         else:
277             vertex = self.geom.MakeVertexOnCurve(path, param)
278             tangent = self.geom.MakeTangentOnCurve(path, param)
279         return (vertex, tangent)
280
281     def _makeSolidPipeFromWires(self, wire1, wire2, point1, point2, path):
282         """
283         Create a solid by the extrusion of section `wire1` to section `wire2`
284         along `path`.
285         """
286         face1 = self.geom.MakeFace(wire1, True)
287         face2 = self.geom.MakeFace(wire2, True)
288         shell = self.geom.MakePipeWithDifferentSections([wire1, wire2],
289                                                         [point1, point2],
290                                                         path, False, False)
291         closedShell = self.geom.MakeShell([face1, face2, shell])
292         solid = self.geom.MakeSolid([closedShell])
293         return solid
294
295     def _buildPart(self):
296         """
297         Build the structural element part.
298         """
299         # Get all the subshapes in the group (normally only edges and wires)
300         paths = self._getSubShapes()
301         listPipes = []
302         withContact = False
303         withCorrection = False
304     
305         for path in paths:
306             # Build the sections (rectangular or circular) at each end of the
307             # beam
308             (fPoint, fNormal) = self._getVertexAndTangentOnOrientedWire(path,
309                                                                         0.0)
310             (lPoint, lNormal) = self._getVertexAndTangentOnOrientedWire(path,
311                                                                         1.0)
312             (outerWire1, innerWire1, outerWire2, innerWire2) = \
313                     self._makeSectionWires(fPoint, fNormal, lPoint, lNormal)
314
315             # Create the resulting solid
316             outerSolid = self._makeSolidPipeFromWires(outerWire1, outerWire2,
317                                                       fPoint, lPoint, path)
318             if self.filling == HOLLOW:
319                 innerSolid = self._makeSolidPipeFromWires(innerWire1,
320                                                           innerWire2, fPoint,
321                                                           lPoint, path)
322                 resultSolid = self.geom.MakeCut(outerSolid, innerSolid)
323                 listPipes.append(resultSolid)
324             else:
325                 listPipes.append(outerSolid)
326
327         if len(listPipes) == 0:
328             return None
329         elif len(listPipes) == 1:
330             return listPipes[0]
331         else:
332             return self.geom.MakeCompound(listPipes)
333
334     def _buildMarkers(self):
335         """
336         Build the markers defining the orientation of the structural element
337         part.
338         """
339         param = 0.5
340         paths = self._getSubShapes()
341         listMarkers = []
342         for path in paths:
343             (center, vecX) = self._getVertexAndTangentOnOrientedWire(path,
344                                                                      param)
345             marker = self._orientation.buildMarker(self.geom, center, vecX)
346             listMarkers.append(marker)
347         return listMarkers
348
349
350 class GeneralBeam(Beam):
351     """
352     This class defines a beam with a generic section. It is represented only
353     as the underlying wire. See class :class:`StructuralElementPart` for the
354     description of the parameters.
355     """
356
357     def __init__(self, studyId, groupName, groupGeomObj, parameters,
358                  name = Beam.DEFAULT_NAME):
359         Beam.__init__(self, studyId, groupName, groupGeomObj, parameters,
360                       name)
361         logger.debug(repr(self))
362
363     def _buildPart(self):
364         """
365         Create a copy of the underlying wire.
366         """
367         edges = self._getSubShapes(1e-7)
368         wire = None
369         if len(edges) > 0:
370             wire = self.geom.MakeWire(edges)
371         return wire
372
373
374 class CircularBeam(Beam):
375     """
376     This class defines a beam with a circular section. It can be full or
377     hollow, and its radius and thickness can vary from one end of the beam to
378     the other. The valid parameters for circular beams are:
379
380     * "R1" or "R": radius at the first end of the beam.
381     * "R2" or "R": radius at the other end of the beam.
382     * "EP1" or "EP" (optional): thickness at the first end of the beam.
383       If not specified or equal to 0, the beam is considered full.
384     * "EP2" or "EP" (optional): thickness at the other end of the beam.
385       If not specified or equal to 0, the beam is considered full.
386
387     See class :class:`StructuralElementPart` for the description of the
388     other parameters.
389
390     """
391
392     def __init__(self, studyId, groupName, groupGeomObj, parameters,
393                  name = Beam.DEFAULT_NAME):
394         Beam.__init__(self, studyId, groupName, groupGeomObj, parameters,
395                       name)
396
397         self.R1 = self._getParameter(["R1", "R"])
398         self.R2 = self._getParameter(["R2", "R"])
399         self.EP1 = self._getParameter(["EP1", "EP"])
400         self.EP2 = self._getParameter(["EP2", "EP"])
401
402         if self.EP1 is None or self.EP2 is None or \
403                                 self.EP1 == 0 or self.EP2 == 0:
404             self.filling = FULL
405         else:
406             self.filling = HOLLOW
407
408         logger.debug(repr(self))
409
410         # Check parameters
411         self._checkSize(self.R1, MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
412                         self._getParamUserName("R1"))
413         self._checkSize(self.R2, MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
414                         self._getParamUserName("R2"))
415         if self.filling == HOLLOW:
416             self._checkSize(self.EP1, MIN_THICKNESS,
417                             self._getParamUserName("EP1"))
418             self._checkSize(self.EP2, MIN_THICKNESS,
419                             self._getParamUserName("EP2"))
420             self._checkSize(self.R1 - self.EP1,
421                             MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
422                             "%s - %s" % (self._getParamUserName("R1"),
423                                          self._getParamUserName("EP1")))
424             self._checkSize(self.R2 - self.EP2,
425                             MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
426                             "%s - %s" % (self._getParamUserName("R2"),
427                                          self._getParamUserName("EP2")))
428
429     def _makeSectionWires(self, fPoint, fNormal, lPoint, lNormal):
430         """
431         Create the circular sections used to build the pipe.
432         """
433         outerCircle1 = self.geom.MakeCircle(fPoint, fNormal, self.R1)
434         outerCircle2 = self.geom.MakeCircle(lPoint, lNormal, self.R2)
435         if self.filling == HOLLOW:
436             innerCircle1 = self.geom.MakeCircle(fPoint, fNormal,
437                                                 self.R1 - self.EP1)
438             innerCircle2 = self.geom.MakeCircle(lPoint, lNormal,
439                                                 self.R2 - self.EP2)
440         else:
441             innerCircle1 = None
442             innerCircle2 = None
443
444         return (outerCircle1, innerCircle1, outerCircle2, innerCircle2)
445
446
447 class RectangularBeam(Beam):
448     """
449     This class defines a beam with a rectangular section. It can be full or
450     hollow, and its dimensions can vary from one end of the beam to the other.
451     The valid parameters for rectangular beams are:
452
453     * "HY1", "HY", "H1" or "H": width at the first end of the beam.
454     * "HZ1", "HZ", "H1" or "H": height at the first end of the beam.
455     * "HY2", "HY", "H2" or "H": width at the other end of the beam.
456     * "HZ2", "HZ", "H2" or "H": height at the other end of the beam.
457     * "EPY1", "EPY", "EP1" or "EP" (optional): thickness in the width
458       direction at the first end of the beam. If not specified or equal to 0,
459       the beam is considered full.
460     * "EPZ1", "EPZ", "EP1" or "EP" (optional): thickness in the height
461       direction at the first end of the beam. If not specified or equal to 0,
462       the beam is considered full.
463     * "EPY2", "EPY", "EP2" or "EP" (optional): thickness in the width
464       direction at the other end of the beam. If not specified or equal to 0,
465       the beam is considered full.
466     * "EPZ2", "EPZ", "EP2" or "EP" (optional): thickness in the height
467       direction at the other end of the beam. If not specified or equal to 0,
468       the beam is considered full.
469
470     See class :class:`StructuralElementPart` for the description of the
471     other parameters.
472
473     """
474
475     def __init__(self, studyId, groupName, groupGeomObj, parameters,
476                  name = Beam.DEFAULT_NAME):
477         Beam.__init__(self, studyId, groupName, groupGeomObj, parameters,
478                       name)
479
480         self.HY1 = self._getParameter(["HY1", "HY", "H1", "H"])
481         self.HZ1 = self._getParameter(["HZ1", "HZ", "H1", "H"])
482         self.HY2 = self._getParameter(["HY2", "HY", "H2", "H"])
483         self.HZ2 = self._getParameter(["HZ2", "HZ", "H2", "H"])
484         self.EPY1 = self._getParameter(["EPY1", "EPY", "EP1", "EP"])
485         self.EPZ1 = self._getParameter(["EPZ1", "EPZ", "EP1", "EP"])
486         self.EPY2 = self._getParameter(["EPY2", "EPY", "EP2", "EP"])
487         self.EPZ2 = self._getParameter(["EPZ2", "EPZ", "EP2", "EP"])
488
489         if self.EPY1 is None or self.EPZ1 is None or \
490            self.EPY2 is None or self.EPZ2 is None or \
491            self.EPY1 == 0 or self.EPZ1 == 0 or \
492            self.EPY2 == 0 or self.EPZ2 == 0:
493             self.filling = FULL
494         else:
495             self.filling = HOLLOW
496
497         logger.debug(repr(self))
498
499         # Check parameters
500         self._checkSize(self.HY1, MIN_DIM_FOR_EXTRUDED_SHAPE,
501                         self._getParamUserName("HY1"))
502         self._checkSize(self.HZ1, MIN_DIM_FOR_EXTRUDED_SHAPE,
503                         self._getParamUserName("HZ1"))
504         self._checkSize(self.HY2, MIN_DIM_FOR_EXTRUDED_SHAPE,
505                         self._getParamUserName("HY2"))
506         self._checkSize(self.HZ2, MIN_DIM_FOR_EXTRUDED_SHAPE,
507                         self._getParamUserName("HZ2"))
508         if self.filling == HOLLOW:
509             self._checkSize(self.EPY1, MIN_THICKNESS,
510                             self._getParamUserName("EPY1"))
511             self._checkSize(self.EPZ1, MIN_THICKNESS,
512                             self._getParamUserName("EPZ1"))
513             self._checkSize(self.EPY2, MIN_THICKNESS,
514                             self._getParamUserName("EPY2"))
515             self._checkSize(self.EPZ2, MIN_THICKNESS,
516                             self._getParamUserName("EPZ2"))
517             self._checkSize(self.HY1 - 2 * self.EPY1,
518                             MIN_DIM_FOR_EXTRUDED_SHAPE,
519                             "%s - 2 * %s" % (self._getParamUserName("HY1"),
520                                              self._getParamUserName("EPY1")))
521             self._checkSize(self.HZ1 - 2 * self.EPZ1,
522                             MIN_DIM_FOR_EXTRUDED_SHAPE,
523                             "%s - 2 * %s" % (self._getParamUserName("HZ1"),
524                                              self._getParamUserName("EPZ1")))
525             self._checkSize(self.HY2 - 2 * self.EPY2,
526                             MIN_DIM_FOR_EXTRUDED_SHAPE,
527                             "%s - 2 * %s" % (self._getParamUserName("HY2"),
528                                              self._getParamUserName("EPY2")))
529             self._checkSize(self.HZ2 - 2 * self.EPZ2,
530                             MIN_DIM_FOR_EXTRUDED_SHAPE,
531                             "%s - 2 * %s" % (self._getParamUserName("HZ2"),
532                                              self._getParamUserName("EPZ2")))
533
534     def _makeRectangle(self, HY, HZ, lcs):
535         """
536         Create a rectangle in the specified plane.
537         """
538         halfHY = HY / 2.0
539         halfHZ = HZ / 2.0
540         sketchStr = "Sketcher:F %g %g:" % (-halfHY, -halfHZ)
541         sketchStr += "TT %g %g:" % (halfHY, -halfHZ)
542         sketchStr += "TT %g %g:" % (halfHY, halfHZ)
543         sketchStr += "TT %g %g:WW" % (-halfHY, halfHZ)
544         logger.debug('Drawing rectangle: "%s"' % sketchStr)
545         sketch = self.geom.MakeSketcherOnPlane(sketchStr, lcs)
546         return sketch
547
548     def _makeSectionRectangles(self, point, vecX, HY, HZ, EPY, EPZ):
549         """
550         Create one side of the rectangular sections used to build the pipe.
551         """
552         (vecY, vecZ) = self._orientation.getVecYZ(self.geom, point, vecX)
553         lcs = self.geom.MakeMarkerPntTwoVec(point, vecY, vecZ)
554         outerRect = self._makeRectangle(HY, HZ, lcs)
555         if self.filling == HOLLOW:
556             innerRect = self._makeRectangle(HY - 2.0 * EPY,
557                                             HZ - 2.0 * EPZ,
558                                             lcs)
559         else:
560             innerRect = None
561         return (outerRect, innerRect)
562
563     def _makeSectionWires(self, fPoint, fNormal, lPoint, lNormal):
564         """
565         Create the rectangular sections used to build the pipe.
566         """
567         (outerRect1, innerRect1) = \
568             self._makeSectionRectangles(fPoint, fNormal, self.HY1, self.HZ1,
569                                         self.EPY1, self.EPZ1)
570         (outerRect2, innerRect2) = \
571             self._makeSectionRectangles(lPoint, lNormal, self.HY2, self.HZ2,
572                                         self.EPY2, self.EPZ2)
573         return (outerRect1, innerRect1, outerRect2, innerRect2)
574
575
576 class StructuralElementPart2D(StructuralElementPart):
577     """
578     This class is an "abstract" class for all 2D structural element parts. It
579     should not be instantiated directly. See class
580     :class:`StructuralElementPart` for the description of the parameters.
581     """
582
583     DEFAULT_NAME = "StructuralElementPart2D"
584
585     def __init__(self, studyId, groupName, groupGeomObj, parameters,
586                  name = DEFAULT_NAME):
587         StructuralElementPart.__init__(self, studyId, groupName, groupGeomObj,
588                                        parameters, name)
589         self._orientation = orientation.Orientation2D(
590                                         self._getParameter(["angleAlpha"]),
591                                         self._getParameter(["angleBeta"]),
592                                         self._getParameter(["Vecteur"]))
593         self.offset = self._getParameter(["Excentre"], 0.0)
594
595     def _makeFaceOffset(self, face, offset, epsilon = 1e-6):
596         """
597         Create a copy of a face at a given offset.
598         """
599         if abs(offset) < epsilon:
600             return self.geom.MakeCopy(face)
601         else:
602             offsetObj = self.geom.MakeOffset(face, offset)
603             # We have to explode the resulting object into faces because it is
604             # created as a polyhedron and not as a single face
605             faces = self.geom.SubShapeAll(offsetObj,
606                                           self.geom.ShapeType["FACE"])
607             return faces[0]
608
609     def _buildMarkersWithOffset(self, offset):
610         """
611         Build the markers for the structural element part with a given offset
612         from the base face.
613         """
614         uParam = 0.5
615         vParam = 0.5
616         listMarkers = []
617         subShapes = self._getSubShapes()
618     
619         for subShape in subShapes:
620             faces = self.geom.SubShapeAll(subShape,
621                                           self.geom.ShapeType["FACE"])
622             for face in faces:
623                 offsetFace = self._makeFaceOffset(face, offset)
624                 # get tangent plane on surface by parameters
625                 center = self.geom.MakeVertexOnSurface(offsetFace,
626                                                        uParam, vParam)
627                 tangPlane = self.geom.MakeTangentPlaneOnFace(offsetFace,
628                                                              uParam, vParam,
629                                                              1.0)
630                 normal = self.geom.GetNormal(tangPlane)
631                 marker = self._orientation.buildMarker(self.geom,
632                                                        center, normal)
633                 listMarkers.append(marker)
634
635         return listMarkers
636
637
638 class ThickShell(StructuralElementPart2D):
639     """
640     This class defines a shell with a given thickness. It can be shifted from
641     the base face. The valid parameters for thick shells are:
642
643     * "Epais": thickness of the shell.
644     * "Excentre": offset of the shell from the base face.
645     * "angleAlpha": angle used to build the markers (see class
646       :class:`~salome.geom.structelem.orientation.Orientation2D`)
647     * "angleBeta": angle used to build the markers (see class
648       :class:`~salome.geom.structelem.orientation.Orientation2D`)
649     * "Vecteur": vector used instead of the angles to build the markers (see
650       class :class:`~salome.geom.structelem.orientation.Orientation2D`)
651
652     See class :class:`StructuralElementPart` for the description of the
653     other parameters.
654     """
655
656     DEFAULT_NAME = "ThickShell"
657
658     def __init__(self, studyId, groupName, groupGeomObj, parameters,
659                  name = DEFAULT_NAME):
660         StructuralElementPart2D.__init__(self, studyId, groupName,
661                                          groupGeomObj, parameters, name)
662         self.thickness = self._getParameter(["Epais"])
663         logger.debug(repr(self))
664
665     def _buildPart(self):
666         """
667         Create the geometrical shapes corresponding to the thick shell.
668         """
669         subShapes = self._getSubShapes()
670         listSolids = []
671     
672         for subShape in subShapes:
673             faces = self.geom.SubShapeAll(subShape,
674                                           self.geom.ShapeType["FACE"])
675             for face in faces:
676                 shape = self._buildThickShellForFace(face)
677                 listSolids.append(shape)
678
679         if len(listSolids) == 0:
680             return None
681         elif len(listSolids) == 1:
682             return listSolids[0]
683         else:
684             return self.geom.MakeCompound(listSolids)
685
686     def _buildThickShellForFace(self, face):
687         """
688         Create the geometrical shapes corresponding to the thick shell for a
689         given face.
690         """
691         epsilon = 1e-6
692         if self.thickness < 2 * epsilon:
693             return self._makeFaceOffset(face, self.offset, epsilon)
694
695         upperOffset = self.offset + self.thickness / 2.0
696         lowerOffset = self.offset - self.thickness / 2.0
697         ruledMode = True
698         modeSolid = False
699
700         upperFace = self._makeFaceOffset(face, upperOffset, epsilon)
701         lowerFace = self._makeFaceOffset(face, lowerOffset, epsilon)
702         listShapes = [upperFace, lowerFace]
703         upperWires = self.geom.SubShapeAll(upperFace,
704                                            self.geom.ShapeType["WIRE"])
705         lowerWires = self.geom.SubShapeAll(lowerFace,
706                                            self.geom.ShapeType["WIRE"])
707         if self.geom.KindOfShape(face)[0] == self.geom.kind.CYLINDER2D:
708             # if the face is a cylinder, we remove the extra side edge
709             upperWires = self._removeCylinderExtraEdge(upperWires)
710             lowerWires = self._removeCylinderExtraEdge(lowerWires)
711         for i in range(len(upperWires)):
712             resShape = self.geom.MakeThruSections([upperWires[i],
713                                                    lowerWires[i]],
714                                                   modeSolid, epsilon,
715                                                   ruledMode)
716             listShapes.append(resShape)
717         resultShell = self.geom.MakeShell(listShapes)
718         resultSolid = self.geom.MakeSolid([resultShell])
719         return resultSolid
720
721     def _removeCylinderExtraEdge(self, wires):
722         """
723         Remove the side edge in a cylinder.
724         """
725         result = []
726         for wire in wires:
727             edges = self.geom.SubShapeAll(wire, self.geom.ShapeType["EDGE"])
728             for edge in edges:
729                 if self.geom.KindOfShape(edge)[0] == self.geom.kind.CIRCLE:
730                     result.append(edge)
731         return result
732
733     def _buildMarkers(self):
734         """
735         Build the markers defining the orientation of the thick shell.
736         """
737         return self._buildMarkersWithOffset(self.offset +
738                                             self.thickness / 2.0)
739
740
741 class Grid(StructuralElementPart2D):
742     """
743     This class defines a grid. A grid is represented by a 2D face patterned
744     with small lines in the main direction of the grid frame. The valid
745     parameters for grids are:
746
747     * "Excentre": offset of the grid from the base face.
748     * "angleAlpha": angle used to build the markers (see class
749       :class:`~salome.geom.structelem.orientation.Orientation2D`)
750     * "angleBeta": angle used to build the markers (see class
751       :class:`~salome.geom.structelem.orientation.Orientation2D`)
752     * "Vecteur": vector used instead of the angles to build the markers (see
753       class :class:`~salome.geom.structelem.orientation.Orientation2D`)
754     * "origAxeX": X coordinate of the origin of the axis used to determine the
755       orientation of the frame in the case of a cylindrical grid.
756     * "origAxeY": Y coordinate of the origin of the axis used to determine the
757       orientation of the frame in the case of a cylindrical grid.
758     * "origAxeZ": Z coordinate of the origin of the axis used to determine the
759       orientation of the frame in the case of a cylindrical grid.
760     * "axeX": X coordinate of the axis used to determine the orientation of
761       the frame in the case of a cylindrical grid.
762     * "axeY": Y coordinate of the axis used to determine the orientation of
763       the frame in the case of a cylindrical grid.
764     * "axeZ": Z coordinate of the axis used to determine the orientation of
765       the frame in the case of a cylindrical grid.
766
767     See class :class:`StructuralElementPart` for the description of the
768     other parameters.
769     """
770
771     DEFAULT_NAME = "Grid"
772
773     def __init__(self, studyId, groupName, groupGeomObj, parameters,
774                  name = DEFAULT_NAME):
775         StructuralElementPart2D.__init__(self, studyId, groupName,
776                                          groupGeomObj, parameters, name)
777         self.xr = self._getParameter(["origAxeX"])
778         self.yr = self._getParameter(["origAxeY"])
779         self.zr = self._getParameter(["origAxeZ"])
780         self.vx = self._getParameter(["axeX"])
781         self.vy = self._getParameter(["axeY"])
782         self.vz = self._getParameter(["axeZ"])
783         logger.debug(repr(self))
784
785     def _buildPart(self):
786         """
787         Create the geometrical shapes representing the grid.
788         """
789         subShapes = self._getSubShapes()
790         listGridShapes = []
791     
792         for subShape in subShapes:
793             faces = self.geom.SubShapeAll(subShape,
794                                           self.geom.ShapeType["FACE"])
795             for face in faces:
796                 if self.geom.KindOfShape(face)[0] == \
797                                         self.geom.kind.CYLINDER2D and \
798                         self.xr is not None and self.yr is not None and \
799                         self.zr is not None and self.vx is not None and \
800                         self.vy is not None and self.vz is not None:
801                     shape = self._buildGridForCylinderFace(face)
802                 else:
803                     shape = self._buildGridForNormalFace(face)
804                 listGridShapes.append(shape)
805
806         if len(listGridShapes) == 0:
807             return None
808         elif len(listGridShapes) == 1:
809             return listGridShapes[0]
810         else:
811             return self.geom.MakeCompound(listGridShapes)
812
813     def _buildGridForNormalFace(self, face):
814         """
815         Create the geometrical shapes representing the grid for a given
816         non-cylindrical face.
817         """
818         baseFace = self._makeFaceOffset(face, self.offset)
819         gridList = [baseFace]
820         
821         # Compute display length for grid elements
822         p1 = self.geom.MakeVertexOnSurface(baseFace, 0.0, 0.0)
823         p2 = self.geom.MakeVertexOnSurface(baseFace, 0.1, 0.1)
824         length = self.geom.MinDistance(p1, p2) / 2.0
825
826         for u in range(1, 10):
827             uParam = u * 0.1
828             for v in range(1, 10):
829                 vParam = v * 0.1
830                 # get tangent plane on surface by parameters
831                 center = self.geom.MakeVertexOnSurface(baseFace,
832                                                        uParam, vParam)
833                 tangPlane = self.geom.MakeTangentPlaneOnFace(baseFace, uParam,
834                                                              vParam, 1.0)
835                 
836                 # use the marker to get the orientation of the frame
837                 normal = self.geom.GetNormal(tangPlane)
838                 marker = self._orientation.buildMarker(self.geom, center,
839                                                        normal, False)
840                 [Ox,Oy,Oz, Zx,Zy,Zz, Xx,Xy,Xz] = self.geom.GetPosition(marker)
841                 xPoint = self.geom.MakeTranslation(center, Xx * length,
842                                                    Xy * length, Xz * length)
843                 gridLine = self.geom.MakeLineTwoPnt(center, xPoint)
844                 gridList.append(gridLine)
845         grid = self.geom.MakeCompound(gridList)
846         return grid
847
848     def _buildGridForCylinderFace(self, face):
849         """
850         Create the geometrical shapes representing the grid for a given
851         cylindrical face.
852         """
853         baseFace = self._makeFaceOffset(face, self.offset)
854         gridList = [baseFace]
855         
856         # Compute display length for grid elements
857         p1 = self.geom.MakeVertexOnSurface(baseFace, 0.0, 0.0)
858         p2 = self.geom.MakeVertexOnSurface(baseFace, 0.1, 0.1)
859         length = self.geom.MinDistance(p1, p2) / 2.0
860         
861         # Create reference vector V
862         origPoint = self.geom.MakeVertex(self.xr, self.yr, self.zr)
863         vPoint = self.geom.MakeTranslation(origPoint,
864                                            self.vx, self.vy, self.vz)
865         refVec = self.geom.MakeVector(origPoint, vPoint)
866
867         for u in range(10):
868             uParam = u * 0.1
869             for v in range(1, 10):
870                 vParam = v * 0.1
871                 
872                 # Compute the local orientation of the frame
873                 center = self.geom.MakeVertexOnSurface(baseFace,
874                                                        uParam, vParam)
875                 locPlaneYZ = self.geom.MakePlaneThreePnt(origPoint, center,
876                                                          vPoint, 1.0)
877                 locOrient = self.geom.GetNormal(locPlaneYZ)
878                 xPoint = self.geom.MakeTranslationVectorDistance(center,
879                                                                  locOrient,
880                                                                  length)
881                 gridLine = self.geom.MakeLineTwoPnt(center, xPoint)
882                 gridList.append(gridLine)
883
884         grid = self.geom.MakeCompound(gridList)
885         return grid
886
887     def _buildMarkers(self):
888         """
889         Create the markers defining the orientation of the grid.
890         """
891         return self._buildMarkersWithOffset(self.offset)
892
893
894 def VisuPoutreGenerale(studyId, groupName, groupGeomObj, parameters,
895                        name = "POUTRE"):
896     """
897     Alias for class :class:`GeneralBeam`.
898     """
899     return GeneralBeam(studyId, groupName, groupGeomObj, parameters, name)
900
901 def VisuPoutreCercle(studyId, groupName, groupGeomObj, parameters,
902                      name = "POUTRE"):
903     """
904     Alias for class :class:`CircularBeam`.
905     """
906     return CircularBeam(studyId, groupName, groupGeomObj, parameters, name)
907   
908 def VisuPoutreRectangle(studyId, groupName, groupGeomObj, parameters,
909                         name = "POUTRE"):
910     """
911     Alias for class :class:`RectangularBeam`.
912     """
913     return RectangularBeam(studyId, groupName, groupGeomObj, parameters, name)
914   
915 def VisuBarreGenerale(studyId, groupName, groupGeomObj, parameters,
916                       name = "BARRE"):
917     """
918     Alias for class :class:`GeneralBeam`.
919     """
920     return GeneralBeam(studyId, groupName, groupGeomObj, parameters, name)
921       
922 def VisuBarreRectangle(studyId, groupName, groupGeomObj, parameters,
923                        name = "BARRE"):
924     """
925     Alias for class :class:`RectangularBeam`.
926     """
927     return RectangularBeam(studyId, groupName, groupGeomObj, parameters, name)
928
929 def VisuBarreCercle(studyId, groupName, groupGeomObj, parameters,
930                     name = "BARRE"):
931     """
932     Alias for class :class:`CircularBeam`.
933     """
934     return CircularBeam(studyId, groupName, groupGeomObj, parameters, name)
935
936 def VisuCable(studyId, groupName, groupGeomObj, parameters, name = "CABLE"):
937     """
938     Alias for class :class:`CircularBeam`.
939     """
940     return CircularBeam(studyId, groupName, groupGeomObj, parameters, name)
941
942 def VisuCoque(studyId, groupName, groupGeomObj, parameters, name = "COQUE"):
943     """
944     Alias for class :class:`ThickShell`.
945     """
946     return ThickShell(studyId, groupName, groupGeomObj, parameters, name)
947   
948 def VisuGrille(studyId, groupName, groupGeomObj, parameters, name = "GRILLE"):
949     """
950     Alias for class :class:`Grid`.
951     """
952     return Grid(studyId, groupName, groupGeomObj, parameters, name)