Salome HOME
*** empty log message ***
[modules/geom.git] / src / GEOM_PY / structelem / parts.py
1 # -*- coding: utf-8 -*-
2 #
3 #  Copyright (C) 2007-2009       EDF R&D
4
5 #    This file is part of PAL_SRC.
6 #
7 #    PAL_SRC is free software; you can redistribute it and/or modify
8 #    it under the terms of the GNU General Public License as published by
9 #    the Free Software Foundation; either version 2 of the License, or
10 #    (at your option) any later version.
11 #
12 #    PAL_SRC is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU General Public License for more details.
16 #
17 #    You should have received a copy of the GNU General Public License
18 #    along with PAL_SRC; if not, write to the Free Software
19 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
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         fParam = 0.
258         lParam = 1.
259         fPoint = self.geom.MakeVertexOnCurve(path, fParam)
260         lPoint = self.geom.MakeVertexOnCurve(path, lParam)
261
262         fNormal = self.geom.MakeTangentOnCurve(path, fParam)
263         lNormal = self.geom.MakeTangentOnCurve(path, lParam)
264
265         fCircle = self.geom.MakeCircle(fPoint, fNormal, 10)
266         lCircle = self.geom.MakeCircle(lPoint, lNormal, 10)
267
268         try:
269             pipe = self.geom.MakePipeWithDifferentSections([fCircle, lCircle],
270                                                            [fPoint, lPoint],
271                                                            path, False, False)
272         except RuntimeError, e:
273             # This dirty trick is needed if the wire is not oriented in the
274             # direction corresponding to parameters 0.0 -> 1.0. In this case,
275             # we catch the error and invert the ends of the wire. This trick
276             # will be removed when the function giving the orientation of an
277             # edge will be added in geompy (see issue 1144 in PAL bugtracker).
278             if (str(e) == "MakePipeWithDifferentSections : First location "
279                           "shapes is not coincided with first vertex of "
280                           "aWirePath"):
281                 return True
282             else:
283                 raise
284         return False
285
286     def _getVertexAndTangentOnOrientedWire(self, path, param):
287         """
288         Get a vertex and the corresponding tangent on a wire by parameter.
289         This method takes into account the "real" orientation of the wire
290         (i.e. the orientation of the underlying OCC object).
291         """
292         if self._isReversed(path):
293             vertex = self.geom.MakeVertexOnCurve(path, 1.0 - param)
294             invtangent = self.geom.MakeTangentOnCurve(path, 1.0 - param)
295             tanpoint = self.geom.MakeTranslationVectorDistance(vertex,
296                                                                invtangent,
297                                                                -1.0)
298             tangent = self.geom.MakeVector(vertex, tanpoint)
299         else:
300             vertex = self.geom.MakeVertexOnCurve(path, param)
301             tangent = self.geom.MakeTangentOnCurve(path, param)
302         return (vertex, tangent)
303
304     def _makeSolidPipeFromWires(self, wire1, wire2, point1, point2, path):
305         """
306         Create a solid by the extrusion of section `wire1` to section `wire2`
307         along `path`.
308         """
309         face1 = self.geom.MakeFace(wire1, True)
310         face2 = self.geom.MakeFace(wire2, True)
311         shell = self.geom.MakePipeWithDifferentSections([wire1, wire2],
312                                                         [point1, point2],
313                                                         path, False, False)
314         closedShell = self.geom.MakeShell([face1, face2, shell])
315         solid = self.geom.MakeSolid([closedShell])
316         return solid
317
318     def _buildPart(self):
319         """
320         Build the structural element part.
321         """
322         # Get all the subshapes in the group (normally only edges and wires)
323         paths = self._getSubShapes()
324         listPipes = []
325         withContact = False
326         withCorrection = False
327     
328         for path in paths:
329             # Build the sections (rectangular or circular) at each end of the
330             # beam
331             (fPoint, fNormal) = self._getVertexAndTangentOnOrientedWire(path,
332                                                                         0.0)
333             (lPoint, lNormal) = self._getVertexAndTangentOnOrientedWire(path,
334                                                                         1.0)
335             (outerWire1, innerWire1, outerWire2, innerWire2) = \
336                     self._makeSectionWires(fPoint, fNormal, lPoint, lNormal)
337
338             # Create the resulting solid
339             outerSolid = self._makeSolidPipeFromWires(outerWire1, outerWire2,
340                                                       fPoint, lPoint, path)
341             if self.filling == HOLLOW:
342                 innerSolid = self._makeSolidPipeFromWires(innerWire1,
343                                                           innerWire2, fPoint,
344                                                           lPoint, path)
345                 resultSolid = self.geom.MakeCut(outerSolid, innerSolid)
346                 listPipes.append(resultSolid)
347             else:
348                 listPipes.append(outerSolid)
349
350         if len(listPipes) == 0:
351             return None
352         elif len(listPipes) == 1:
353             return listPipes[0]
354         else:
355             return self.geom.MakeCompound(listPipes)
356
357     def _buildMarkers(self):
358         """
359         Build the markers defining the orientation of the structural element
360         part.
361         """
362         param = 0.5
363         paths = self._getSubShapes()
364         listMarkers = []
365         for path in paths:
366             (center, vecX) = self._getVertexAndTangentOnOrientedWire(path,
367                                                                      param)
368             marker = self._orientation.buildMarker(self.geom, center, vecX)
369             listMarkers.append(marker)
370         return listMarkers
371
372
373 class GeneralBeam(Beam):
374     """
375     This class defines a beam with a generic section. It is represented only
376     as the underlying wire. See class :class:`StructuralElementPart` for the
377     description of the parameters.
378     """
379
380     def __init__(self, studyId, groupName, groupGeomObj, parameters,
381                  name = Beam.DEFAULT_NAME):
382         Beam.__init__(self, studyId, groupName, groupGeomObj, parameters,
383                       name)
384         logger.debug(repr(self))
385
386     def _buildPart(self):
387         """
388         Create a copy of the underlying wire.
389         """
390         edges = self._getSubShapes(1e-7)
391         wire = None
392         if len(edges) > 0:
393             wire = self.geom.MakeWire(edges)
394         return wire
395
396
397 class CircularBeam(Beam):
398     """
399     This class defines a beam with a circular section. It can be full or
400     hollow, and its radius and thickness can vary from one end of the beam to
401     the other. The valid parameters for circular beams are:
402
403     * "R1" or "R": radius at the first end of the beam.
404     * "R2" or "R": radius at the other end of the beam.
405     * "EP1" or "EP" (optional): thickness at the first end of the beam.
406       If not specified or equal to 0, the beam is considered full.
407     * "EP2" or "EP" (optional): thickness at the other end of the beam.
408       If not specified or equal to 0, the beam is considered full.
409
410     See class :class:`StructuralElementPart` for the description of the
411     other parameters.
412
413     """
414
415     def __init__(self, studyId, groupName, groupGeomObj, parameters,
416                  name = Beam.DEFAULT_NAME):
417         Beam.__init__(self, studyId, groupName, groupGeomObj, parameters,
418                       name)
419
420         self.R1 = self._getParameter(["R1", "R"])
421         self.R2 = self._getParameter(["R2", "R"])
422         self.EP1 = self._getParameter(["EP1", "EP"])
423         self.EP2 = self._getParameter(["EP2", "EP"])
424
425         if self.EP1 is None or self.EP2 is None or \
426                                 self.EP1 == 0 or self.EP2 == 0:
427             self.filling = FULL
428         else:
429             self.filling = HOLLOW
430
431         logger.debug(repr(self))
432
433         # Check parameters
434         self._checkSize(self.R1, MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
435                         self._getParamUserName("R1"))
436         self._checkSize(self.R2, MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
437                         self._getParamUserName("R2"))
438         if self.filling == HOLLOW:
439             self._checkSize(self.EP1, MIN_THICKNESS,
440                             self._getParamUserName("EP1"))
441             self._checkSize(self.EP2, MIN_THICKNESS,
442                             self._getParamUserName("EP2"))
443             self._checkSize(self.R1 - self.EP1,
444                             MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
445                             "%s - %s" % (self._getParamUserName("R1"),
446                                          self._getParamUserName("EP1")))
447             self._checkSize(self.R2 - self.EP2,
448                             MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0,
449                             "%s - %s" % (self._getParamUserName("R2"),
450                                          self._getParamUserName("EP2")))
451
452     def _makeSectionWires(self, fPoint, fNormal, lPoint, lNormal):
453         """
454         Create the circular sections used to build the pipe.
455         """
456         outerCircle1 = self.geom.MakeCircle(fPoint, fNormal, self.R1)
457         outerCircle2 = self.geom.MakeCircle(lPoint, lNormal, self.R2)
458         if self.filling == HOLLOW:
459             innerCircle1 = self.geom.MakeCircle(fPoint, fNormal,
460                                                 self.R1 - self.EP1)
461             innerCircle2 = self.geom.MakeCircle(lPoint, lNormal,
462                                                 self.R2 - self.EP2)
463         else:
464             innerCircle1 = None
465             innerCircle2 = None
466
467         return (outerCircle1, innerCircle1, outerCircle2, innerCircle2)
468
469
470 class RectangularBeam(Beam):
471     """
472     This class defines a beam with a rectangular section. It can be full or
473     hollow, and its dimensions can vary from one end of the beam to the other.
474     The valid parameters for rectangular beams are:
475
476     * "HY1", "HY", "H1" or "H": width at the first end of the beam.
477     * "HZ1", "HZ", "H1" or "H": height at the first end of the beam.
478     * "HY2", "HY", "H2" or "H": width at the other end of the beam.
479     * "HZ2", "HZ", "H2" or "H": height at the other end of the beam.
480     * "EPY1", "EPY", "EP1" or "EP" (optional): thickness in the width
481       direction at the first end of the beam. If not specified or equal to 0,
482       the beam is considered full.
483     * "EPZ1", "EPZ", "EP1" or "EP" (optional): thickness in the height
484       direction at the first end of the beam. If not specified or equal to 0,
485       the beam is considered full.
486     * "EPY2", "EPY", "EP2" or "EP" (optional): thickness in the width
487       direction at the other end of the beam. If not specified or equal to 0,
488       the beam is considered full.
489     * "EPZ2", "EPZ", "EP2" or "EP" (optional): thickness in the height
490       direction at the other end of the beam. If not specified or equal to 0,
491       the beam is considered full.
492
493     See class :class:`StructuralElementPart` for the description of the
494     other parameters.
495
496     """
497
498     def __init__(self, studyId, groupName, groupGeomObj, parameters,
499                  name = Beam.DEFAULT_NAME):
500         Beam.__init__(self, studyId, groupName, groupGeomObj, parameters,
501                       name)
502
503         self.HY1 = self._getParameter(["HY1", "HY", "H1", "H"])
504         self.HZ1 = self._getParameter(["HZ1", "HZ", "H1", "H"])
505         self.HY2 = self._getParameter(["HY2", "HY", "H2", "H"])
506         self.HZ2 = self._getParameter(["HZ2", "HZ", "H2", "H"])
507         self.EPY1 = self._getParameter(["EPY1", "EPY", "EP1", "EP"])
508         self.EPZ1 = self._getParameter(["EPZ1", "EPZ", "EP1", "EP"])
509         self.EPY2 = self._getParameter(["EPY2", "EPY", "EP2", "EP"])
510         self.EPZ2 = self._getParameter(["EPZ2", "EPZ", "EP2", "EP"])
511
512         if self.EPY1 is None or self.EPZ1 is None or \
513            self.EPY2 is None or self.EPZ2 is None or \
514            self.EPY1 == 0 or self.EPZ1 == 0 or \
515            self.EPY2 == 0 or self.EPZ2 == 0:
516             self.filling = FULL
517         else:
518             self.filling = HOLLOW
519
520         logger.debug(repr(self))
521
522         # Check parameters
523         self._checkSize(self.HY1, MIN_DIM_FOR_EXTRUDED_SHAPE,
524                         self._getParamUserName("HY1"))
525         self._checkSize(self.HZ1, MIN_DIM_FOR_EXTRUDED_SHAPE,
526                         self._getParamUserName("HZ1"))
527         self._checkSize(self.HY2, MIN_DIM_FOR_EXTRUDED_SHAPE,
528                         self._getParamUserName("HY2"))
529         self._checkSize(self.HZ2, MIN_DIM_FOR_EXTRUDED_SHAPE,
530                         self._getParamUserName("HZ2"))
531         if self.filling == HOLLOW:
532             self._checkSize(self.EPY1, MIN_THICKNESS,
533                             self._getParamUserName("EPY1"))
534             self._checkSize(self.EPZ1, MIN_THICKNESS,
535                             self._getParamUserName("EPZ1"))
536             self._checkSize(self.EPY2, MIN_THICKNESS,
537                             self._getParamUserName("EPY2"))
538             self._checkSize(self.EPZ2, MIN_THICKNESS,
539                             self._getParamUserName("EPZ2"))
540             self._checkSize(self.HY1 - 2 * self.EPY1,
541                             MIN_DIM_FOR_EXTRUDED_SHAPE,
542                             "%s - 2 * %s" % (self._getParamUserName("HY1"),
543                                              self._getParamUserName("EPY1")))
544             self._checkSize(self.HZ1 - 2 * self.EPZ1,
545                             MIN_DIM_FOR_EXTRUDED_SHAPE,
546                             "%s - 2 * %s" % (self._getParamUserName("HZ1"),
547                                              self._getParamUserName("EPZ1")))
548             self._checkSize(self.HY2 - 2 * self.EPY2,
549                             MIN_DIM_FOR_EXTRUDED_SHAPE,
550                             "%s - 2 * %s" % (self._getParamUserName("HY2"),
551                                              self._getParamUserName("EPY2")))
552             self._checkSize(self.HZ2 - 2 * self.EPZ2,
553                             MIN_DIM_FOR_EXTRUDED_SHAPE,
554                             "%s - 2 * %s" % (self._getParamUserName("HZ2"),
555                                              self._getParamUserName("EPZ2")))
556
557     def _makeRectangle(self, HY, HZ, planeSect):
558         """
559         Create a rectangle in the specified plane.
560         """
561         halfHY = HY / 2.0
562         halfHZ = HZ / 2.0
563         sketchStr = "Sketcher:F %g" % (-halfHZ) + " %g" % (-halfHY) + ":"
564         sketchStr += "TT %g" % (halfHZ) + " %g" % (-halfHY) + ":"
565         sketchStr += "TT %g" % (halfHZ) + " %g" % (halfHY) + ":" 
566         sketchStr += "TT %g" % (-halfHZ) + " %g" % (halfHY) + ":WW"
567         logger.debug('Drawing rectangle: "%s"' % sketchStr)
568         sketch = self.geom.MakeSketcherOnPlane(sketchStr, planeSect)
569         return sketch
570
571     def _makeSectionWires(self, fPoint, fNormal, lPoint, lNormal):
572         """
573         Create the rectangular sections used to build the pipe.
574         """
575         planeSect1 = self.geom.MakePlane(fPoint, fNormal, 1.0)
576         outerRect1 = self._makeRectangle(self.HY1, self.HZ1, planeSect1)
577         planeSect2 = self.geom.MakePlane(lPoint, lNormal, 1.0)
578         outerRect2 = self._makeRectangle(self.HY2, self.HZ2, planeSect2)
579         if self.filling == HOLLOW:
580             innerRect1 = self._makeRectangle(self.HY1 - 2 * self.EPY1,
581                                              self.HZ1 - 2 * self.EPZ1,
582                                              planeSect1)
583             innerRect2 = self._makeRectangle(self.HY2 - 2 * self.EPY2,
584                                              self.HZ2 - 2 * self.EPZ2,
585                                              planeSect2)
586         else:
587             innerRect1 = None
588             innerRect2 = None
589
590         return (outerRect1, innerRect1, outerRect2, innerRect2)
591
592
593 class StructuralElementPart2D(StructuralElementPart):
594     """
595     This class is an "abstract" class for all 2D structural element parts. It
596     should not be instantiated directly. See class
597     :class:`StructuralElementPart` for the description of the parameters.
598     """
599
600     DEFAULT_NAME = "StructuralElementPart2D"
601
602     def __init__(self, studyId, groupName, groupGeomObj, parameters,
603                  name = DEFAULT_NAME):
604         StructuralElementPart.__init__(self, studyId, groupName, groupGeomObj,
605                                        parameters, name)
606         self._orientation = orientation.Orientation2D(
607                                         self._getParameter(["angleAlpha"]),
608                                         self._getParameter(["angleBeta"]),
609                                         self._getParameter(["Vecteur"]))
610         self.offset = self._getParameter(["Excentre"], 0.0)
611
612     def _makeFaceOffset(self, face, offset, epsilon = 1e-6):
613         """
614         Create a copy of a face at a given offset.
615         """
616         if abs(offset) < epsilon:
617             return self.geom.MakeCopy(face)
618         else:
619             offsetObj = self.geom.MakeOffset(face, offset)
620             # We have to explode the resulting object into faces because it is
621             # created as a polyhedron and not as a single face
622             faces = self.geom.SubShapeAll(offsetObj,
623                                           self.geom.ShapeType["FACE"])
624             return faces[0]
625
626     def _buildMarkersWithOffset(self, offset):
627         """
628         Build the markers for the structural element part with a given offset
629         from the base face.
630         """
631         uParam = 0.5
632         vParam = 0.5
633         listMarkers = []
634         subShapes = self._getSubShapes()
635     
636         for subShape in subShapes:
637             faces = self.geom.SubShapeAll(subShape,
638                                           self.geom.ShapeType["FACE"])
639             for face in faces:
640                 offsetFace = self._makeFaceOffset(face, offset)
641                 # get tangent plane on surface by parameters
642                 center = self.geom.MakeVertexOnSurface(offsetFace,
643                                                        uParam, vParam)
644                 tangPlane = self.geom.MakeTangentPlaneOnFace(offsetFace,
645                                                              uParam, vParam,
646                                                              1.0)
647                 normal = self.geom.GetNormal(tangPlane)
648                 marker = self._orientation.buildMarker(self.geom,
649                                                        center, normal)
650                 listMarkers.append(marker)
651
652         return listMarkers
653
654
655 class ThickShell(StructuralElementPart2D):
656     """
657     This class defines a shell with a given thickness. It can be shifted from
658     the base face. The valid parameters for thick shells are:
659
660     * "Epais": thickness of the shell.
661     * "Excentre": offset of the shell from the base face.
662     * "angleAlpha": angle used to build the markers (see class
663       :class:`~salome.geom.structelem.orientation.Orientation2D`)
664     * "angleBeta": angle used to build the markers (see class
665       :class:`~salome.geom.structelem.orientation.Orientation2D`)
666     * "Vecteur": vector used instead of the angles to build the markers (see
667       class :class:`~salome.geom.structelem.orientation.Orientation2D`)
668
669     See class :class:`StructuralElementPart` for the description of the
670     other parameters.
671     """
672
673     DEFAULT_NAME = "ThickShell"
674
675     def __init__(self, studyId, groupName, groupGeomObj, parameters,
676                  name = DEFAULT_NAME):
677         StructuralElementPart2D.__init__(self, studyId, groupName,
678                                          groupGeomObj, parameters, name)
679         self.thickness = self._getParameter(["Epais"])
680         logger.debug(repr(self))
681
682     def _buildPart(self):
683         """
684         Create the geometrical shapes corresponding to the thick shell.
685         """
686         subShapes = self._getSubShapes()
687         listSolids = []
688     
689         for subShape in subShapes:
690             faces = self.geom.SubShapeAll(subShape,
691                                           self.geom.ShapeType["FACE"])
692             for face in faces:
693                 shape = self._buildThickShellForFace(face)
694                 listSolids.append(shape)
695
696         if len(listSolids) == 0:
697             return None
698         elif len(listSolids) == 1:
699             return listSolids[0]
700         else:
701             return self.geom.MakeCompound(listSolids)
702
703     def _buildThickShellForFace(self, face):
704         """
705         Create the geometrical shapes corresponding to the thick shell for a
706         given face.
707         """
708         epsilon = 1e-6
709         if self.thickness < 2 * epsilon:
710             return self._makeFaceOffset(face, self.offset, epsilon)
711
712         upperOffset = self.offset + self.thickness / 2.0
713         lowerOffset = self.offset - self.thickness / 2.0
714         ruledMode = True
715         modeSolid = False
716
717         upperFace = self._makeFaceOffset(face, upperOffset, epsilon)
718         lowerFace = self._makeFaceOffset(face, lowerOffset, epsilon)
719         listShapes = [upperFace, lowerFace]
720         upperWires = self.geom.SubShapeAll(upperFace,
721                                            self.geom.ShapeType["WIRE"])
722         lowerWires = self.geom.SubShapeAll(lowerFace,
723                                            self.geom.ShapeType["WIRE"])
724         if self.geom.KindOfShape(face)[0] == self.geom.kind.CYLINDER2D:
725             # if the face is a cylinder, we remove the extra side edge
726             upperWires = self._removeCylinderExtraEdge(upperWires)
727             lowerWires = self._removeCylinderExtraEdge(lowerWires)
728         for i in range(len(upperWires)):
729             resShape = self.geom.MakeThruSections([upperWires[i],
730                                                    lowerWires[i]],
731                                                   modeSolid, epsilon,
732                                                   ruledMode)
733             listShapes.append(resShape)
734         resultShell = self.geom.MakeShell(listShapes)
735         resultSolid = self.geom.MakeSolid([resultShell])
736         return resultSolid
737
738     def _removeCylinderExtraEdge(self, wires):
739         """
740         Remove the side edge in a cylinder.
741         """
742         result = []
743         for wire in wires:
744             edges = self.geom.SubShapeAll(wire, self.geom.ShapeType["EDGE"])
745             for edge in edges:
746                 if self.geom.KindOfShape(edge)[0] == self.geom.kind.CIRCLE:
747                     result.append(edge)
748         return result
749
750     def _buildMarkers(self):
751         """
752         Build the markers defining the orientation of the thick shell.
753         """
754         return self._buildMarkersWithOffset(self.offset +
755                                             self.thickness / 2.0)
756
757
758 class Grid(StructuralElementPart2D):
759     """
760     This class defines a grid. A grid is represented by a 2D face patterned
761     with small lines in the main direction of the grid frame. The valid
762     parameters for grids are:
763
764     * "Excentre": offset of the grid from the base face.
765     * "angleAlpha": angle used to build the markers (see class
766       :class:`~salome.geom.structelem.orientation.Orientation2D`)
767     * "angleBeta": angle used to build the markers (see class
768       :class:`~salome.geom.structelem.orientation.Orientation2D`)
769     * "Vecteur": vector used instead of the angles to build the markers (see
770       class :class:`~salome.geom.structelem.orientation.Orientation2D`)
771     * "origAxeX": X coordinate of the origin of the axis used to determine the
772       orientation of the frame in the case of a cylindrical grid.
773     * "origAxeY": Y coordinate of the origin of the axis used to determine the
774       orientation of the frame in the case of a cylindrical grid.
775     * "origAxeZ": Z coordinate of the origin of the axis used to determine the
776       orientation of the frame in the case of a cylindrical grid.
777     * "axeX": X coordinate of the axis used to determine the orientation of
778       the frame in the case of a cylindrical grid.
779     * "axeY": Y coordinate of the axis used to determine the orientation of
780       the frame in the case of a cylindrical grid.
781     * "axeZ": Z coordinate of the axis used to determine the orientation of
782       the frame in the case of a cylindrical grid.
783
784     See class :class:`StructuralElementPart` for the description of the
785     other parameters.
786     """
787
788     DEFAULT_NAME = "Grid"
789
790     def __init__(self, studyId, groupName, groupGeomObj, parameters,
791                  name = DEFAULT_NAME):
792         StructuralElementPart2D.__init__(self, studyId, groupName,
793                                          groupGeomObj, parameters, name)
794         self.xr = self._getParameter(["origAxeX"])
795         self.yr = self._getParameter(["origAxeY"])
796         self.zr = self._getParameter(["origAxeZ"])
797         self.vx = self._getParameter(["axeX"])
798         self.vy = self._getParameter(["axeY"])
799         self.vz = self._getParameter(["axeZ"])
800         logger.debug(repr(self))
801
802     def _buildPart(self):
803         """
804         Create the geometrical shapes representing the grid.
805         """
806         subShapes = self._getSubShapes()
807         listGridShapes = []
808     
809         for subShape in subShapes:
810             faces = self.geom.SubShapeAll(subShape,
811                                           self.geom.ShapeType["FACE"])
812             for face in faces:
813                 if self.geom.KindOfShape(face)[0] == \
814                                         self.geom.kind.CYLINDER2D and \
815                         self.xr is not None and self.yr is not None and \
816                         self.zr is not None and self.vx is not None and \
817                         self.vy is not None and self.vz is not None:
818                     shape = self._buildGridForCylinderFace(face)
819                 else:
820                     shape = self._buildGridForNormalFace(face)
821                 listGridShapes.append(shape)
822
823         if len(listGridShapes) == 0:
824             return None
825         elif len(listGridShapes) == 1:
826             return listGridShapes[0]
827         else:
828             return self.geom.MakeCompound(listGridShapes)
829
830     def _buildGridForNormalFace(self, face):
831         """
832         Create the geometrical shapes representing the grid for a given
833         non-cylindrical face.
834         """
835         baseFace = self._makeFaceOffset(face, self.offset)
836         gridList = [baseFace]
837         
838         # Compute display length for grid elements
839         p1 = self.geom.MakeVertexOnSurface(baseFace, 0.0, 0.0)
840         p2 = self.geom.MakeVertexOnSurface(baseFace, 0.1, 0.1)
841         length = self.geom.MinDistance(p1, p2) / 2.0
842
843         for u in range(1, 10):
844             uParam = u * 0.1
845             for v in range(1, 10):
846                 vParam = v * 0.1
847                 # get tangent plane on surface by parameters
848                 center = self.geom.MakeVertexOnSurface(baseFace,
849                                                        uParam, vParam)
850                 tangPlane = self.geom.MakeTangentPlaneOnFace(baseFace, uParam,
851                                                              vParam, 1.0)
852                 
853                 # use the marker to get the orientation of the frame
854                 normal = self.geom.GetNormal(tangPlane)
855                 marker = self._orientation.buildMarker(self.geom, center,
856                                                        normal, False)
857                 [Ox,Oy,Oz, Zx,Zy,Zz, Xx,Xy,Xz] = self.geom.GetPosition(marker)
858                 xPoint = self.geom.MakeTranslation(center, Xx * length,
859                                                    Xy * length, Xz * length)
860                 gridLine = self.geom.MakeLineTwoPnt(center, xPoint)
861                 gridList.append(gridLine)
862         grid = self.geom.MakeCompound(gridList)
863         return grid
864
865     def _buildGridForCylinderFace(self, face):
866         """
867         Create the geometrical shapes representing the grid for a given
868         cylindrical face.
869         """
870         baseFace = self._makeFaceOffset(face, self.offset)
871         gridList = [baseFace]
872         
873         # Compute display length for grid elements
874         p1 = self.geom.MakeVertexOnSurface(baseFace, 0.0, 0.0)
875         p2 = self.geom.MakeVertexOnSurface(baseFace, 0.1, 0.1)
876         length = self.geom.MinDistance(p1, p2) / 2.0
877         
878         # Create reference vector V
879         origPoint = self.geom.MakeVertex(self.xr, self.yr, self.zr)
880         vPoint = self.geom.MakeTranslation(origPoint,
881                                            self.vx, self.vy, self.vz)
882         refVec = self.geom.MakeVector(origPoint, vPoint)
883
884         for u in range(10):
885             uParam = u * 0.1
886             for v in range(1, 10):
887                 vParam = v * 0.1
888                 
889                 # Compute the local orientation of the frame
890                 center = self.geom.MakeVertexOnSurface(baseFace,
891                                                        uParam, vParam)
892                 locPlaneYZ = self.geom.MakePlaneThreePnt(origPoint, center,
893                                                          vPoint, 1.0)
894                 locOrient = self.geom.GetNormal(locPlaneYZ)
895                 xPoint = self.geom.MakeTranslationVectorDistance(center,
896                                                                  locOrient,
897                                                                  length)
898                 gridLine = self.geom.MakeLineTwoPnt(center, xPoint)
899                 gridList.append(gridLine)
900
901         grid = self.geom.MakeCompound(gridList)
902         return grid
903
904     def _buildMarkers(self):
905         """
906         Create the markers defining the orientation of the grid.
907         """
908         return self._buildMarkersWithOffset(self.offset)
909
910
911 def VisuPoutreGenerale(studyId, groupName, groupGeomObj, parameters,
912                        name = "POUTRE"):
913     """
914     Alias for class :class:`GeneralBeam`.
915     """
916     return GeneralBeam(studyId, groupName, groupGeomObj, parameters, name)
917
918 def VisuPoutreCercle(studyId, groupName, groupGeomObj, parameters,
919                      name = "POUTRE"):
920     """
921     Alias for class :class:`CircularBeam`.
922     """
923     return CircularBeam(studyId, groupName, groupGeomObj, parameters, name)
924   
925 def VisuPoutreRectangle(studyId, groupName, groupGeomObj, parameters,
926                         name = "POUTRE"):
927     """
928     Alias for class :class:`RectangularBeam`.
929     """
930     return RectangularBeam(studyId, groupName, groupGeomObj, parameters, name)
931   
932 def VisuBarreGenerale(studyId, groupName, groupGeomObj, parameters,
933                       name = "BARRE"):
934     """
935     Alias for class :class:`GeneralBeam`.
936     """
937     return GeneralBeam(studyId, groupName, groupGeomObj, parameters, name)
938       
939 def VisuBarreRectangle(studyId, groupName, groupGeomObj, parameters,
940                        name = "BARRE"):
941     """
942     Alias for class :class:`RectangularBeam`.
943     """
944     return RectangularBeam(studyId, groupName, groupGeomObj, parameters, name)
945
946 def VisuBarreCercle(studyId, groupName, groupGeomObj, parameters,
947                     name = "BARRE"):
948     """
949     Alias for class :class:`CircularBeam`.
950     """
951     return CircularBeam(studyId, groupName, groupGeomObj, parameters, name)
952
953 def VisuCable(studyId, groupName, groupGeomObj, parameters, name = "CABLE"):
954     """
955     Alias for class :class:`CircularBeam`.
956     """
957     return CircularBeam(studyId, groupName, groupGeomObj, parameters, name)
958
959 def VisuCoque(studyId, groupName, groupGeomObj, parameters, name = "COQUE"):
960     """
961     Alias for class :class:`ThickShell`.
962     """
963     return ThickShell(studyId, groupName, groupGeomObj, parameters, name)
964   
965 def VisuGrille(studyId, groupName, groupGeomObj, parameters, name = "GRILLE"):
966     """
967     Alias for class :class:`Grid`.
968     """
969     return Grid(studyId, groupName, groupGeomObj, parameters, name)