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