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