Salome HOME
Fix an error message in structural element visualization
[modules/geom.git] / src / GEOM_PY / structelem / __init__.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2007-2013  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 package is used to create and visualize structural elements. It contains
23 three modules:
24
25 * This module :mod:`salome.geom.structelem` defines the main classes
26   :class:`StructuralElement` and :class:`StructuralElementManager` that can be
27   directly used to build structural elements.
28 * The module :mod:`salome.geom.structelem.parts` defines the classes corresponding to
29   the different parts (beams, grids, etc.) that make up a structural element.
30   It is used to build the geometric shapes in the structural element.
31 * The module :mod:`salome.geom.structelem.orientation` defines the classes that are
32   used to compute the orientation of the structural element parts and to build
33   the corresponding markers.
34
35 A structural element is a set of geometric shapes (beams, grids, etc.) that
36 are built semi-automatically along a set of geometric primitives (edges for
37 instance). They are visualized with the same color as their base primitives in
38 the geom viewer.
39
40 Structural elements are generally created by the
41 :class:`StructuralElementManager` class, from a list of commands describing
42 the element to create.
43
44 Example::
45
46     commandList = [('VisuPoutreGenerale', {'Group_Maille': 'Edge_1'}),
47                    ('VisuBarreCercle',
48                     {'R': 30, 'Group_Maille': 'Edge_1', 'EP': 15}),
49                   ]
50
51     structElemManager = StructuralElementManager()
52     elem = structElemManager.createElement(commandList)
53     elem.display()
54     salome.sg.updateObjBrowser(True)
55
56 """
57
58 import types
59
60 import salome
61
62 from salome.kernel.logger import Logger
63 from salome.kernel import termcolor
64 logger = Logger("salome.geom.structelem", color = termcolor.RED)
65 from salome.kernel.studyedit import getStudyEditor
66
67 __all__ = ["parts", "orientation"]
68
69 from salome.geom.structelem import parts
70 from salome.geom.structelem.parts import InvalidParameterError
71
72 class StructuralElementManager:
73     """
74     This class manages the structural elements in the study. It is used to
75     create a new structural element from a list of commands. The parameter
76     `studyId` defines the ID of the study in which the manager will create
77     structural elements. If it is :const:`None` or not specified, it will use
78     the ID of the current study as defined by
79     :func:`salome.kernel.studyedit.getActiveStudyId` function.
80     """
81     def __init__(self, studyId = None):
82         self._studyEditor = getStudyEditor(studyId)
83
84     def createElement(self, commandList):
85         """
86         Create a structural element from the list of commands `commandList`.
87         Each command in this list represent a part of the structural element,
88         that is a specific kind of shape (circular beam, grid, etc.)
89         associated with one or several geometrical primitives. A command must
90         be a tuple. The first element is the structural element part class
91         name or alias name. The second element is a dictionary containing the
92         parameters describing the part. Valid class names are all the classes
93         defined in the module :mod:`~salome.geom.structelem.parts` and inheriting
94         class :class:`~parts.StructuralElementPart`. There are also several
95         aliases for backward compatibility. Here is the complete list:
96         
97         * :class:`~parts.GeneralBeam`
98         * :class:`~parts.CircularBeam`
99         * :class:`~parts.RectangularBeam`
100         * :class:`~parts.ThickShell`
101         * :class:`~parts.Grid`
102
103         * :func:`~parts.VisuPoutreGenerale` (alias for
104           :class:`~parts.GeneralBeam`)
105         * :func:`~parts.VisuPoutreCercle` (alias for
106           :class:`~parts.CircularBeam`)
107         * :func:`~parts.VisuPoutreRectangle` (alias for
108           :class:`~parts.RectangularBeam`)
109         * :func:`~parts.VisuBarreGenerale` (alias for
110           :class:`~parts.GeneralBeam`)
111         * :func:`~parts.VisuBarreRectangle` (alias for
112           :class:`~parts.RectangularBeam`)
113         * :func:`~parts.VisuBarreCercle` (alias for
114           :class:`~parts.CircularBeam`)
115         * :func:`~parts.VisuCable` (alias for :class:`~parts.CircularBeam`)
116         * :func:`~parts.VisuCoque` (alias for :class:`~parts.ThickShell`)
117         * :func:`~parts.VisuGrille` (alias for :class:`~parts.Grid`)
118         
119         * ``Orientation``: This identifier is used to specify the orientation
120           of one or several 1D structural element parts (i.e. beams). The
121           parameters are described in class
122           :class:`~orientation.Orientation1D`.
123
124         The valid parameters in the dictionary depend on the type of the
125         structural element part, and are detailed in the documentation of
126         the corresponding class. The only parameter that is common to all the
127         classes is "MeshGroups" (that can also be named "Group_Maille"). It
128         defines the name of the geometrical object(s) in the study that will
129         be used as primitives to build the structural element part. This
130         parameter can be either a list of strings or a single string with
131         comma separated names.
132         """
133         logger.debug("StructuralElementManager.createElement: START")
134         logger.debug("Command list: %s" % commandList)
135
136         element = StructuralElement(self._studyEditor.studyId)
137         orientationCmdList = []
138         for command in commandList:
139             (parttype, parameters) = command
140             if parttype == "Orientation":
141                 orientationCmdList += [command]
142             elif parttype not in dir(parts):
143                 logger.warning('Invalid structural element part name "%s"'
144                                ' in command %s, this command will be '
145                                'ignored.' % (parttype, command))
146             else:
147                 (meshGroupList, newparams) = self._extractMeshGroups(command)
148                 for meshGroup in meshGroupList:
149                     # Get the geometrical primitive object
150                     if meshGroup.startswith('/'):
151                         groupSObj = self._studyEditor.study.FindObjectByPath(meshGroup)
152                         meshGroup = meshGroup.split('/')[-1]
153                         pass
154                     else:
155                         groupSObj = self._studyEditor.study.FindObject(meshGroup)
156                         pass
157                     groupGeomObj = None
158                     if groupSObj is not None:
159                         groupGeomObj = \
160                                 self._studyEditor.getOrLoadObject(groupSObj)
161                     if groupGeomObj is None:
162                         logger.error("Can't get geom object corresponding to "
163                                      'mesh group "%s", structural element '
164                                      "part %s will not be built." %
165                                      (meshGroup, command))
166                         continue
167                     
168                     # Create the part
169                     try:
170                         part = parts.__dict__[parttype](
171                                         self._studyEditor.studyId, meshGroup,
172                                         groupGeomObj, newparams)
173                         element.addPart(part)
174                     except InvalidParameterError, e:
175                         logger.error("Invalid parameter error: %s" % e)
176                         raise
177                     except:
178                         logger.exception("Can't create structural element"
179                                          " part with command %s." %
180                                          str(command))
181
182         # Orientations are parsed after the parts because they must be
183         # associated with existing parts.
184         for command in orientationCmdList:
185             (parttype, parameters) = command
186             (meshGroupList, orientParams) = self._extractMeshGroups(command)
187             for meshGroup in meshGroupList:
188                 element.addOrientation(meshGroup, orientParams)
189
190         element.build()
191         logger.debug("StructuralElementManager.createElement: END")
192         return element
193     
194     def _extractMeshGroups(self, command):
195         """
196         This method extracts the names of the mesh groups (i.e. the
197         geometrical objects used to build the structural element part) in the
198         command in parameter. It returns a tuple containing the mesh groups as
199         a list of strings and the other parameters of the command as a new
200         dictionary.
201         """
202         (parttype, parameters) = command
203         newparams = parameters.copy()
204         groupMailleParam = newparams.pop("Group_Maille", None)
205         meshGroupParam = newparams.pop("MeshGroups", None)
206         if groupMailleParam is None and meshGroupParam is None:
207             logger.warning("No mesh group specified in command %s, this "
208                            "command will be ignored." % command)
209             return ([], newparams)
210         elif groupMailleParam is not None and meshGroupParam is not None:
211             logger.warning('Both "MeshGroups" and "Group_Maille" specified in'
212                            ' command %s, only "MeshGroups" will be used.' %
213                            command)
214         elif groupMailleParam is not None and meshGroupParam is None:
215             meshGroupParam = groupMailleParam
216         
217         meshGroupList = []
218         if type(meshGroupParam) == types.StringType:
219             meshGroupList = self._getMeshGroupListFromString(meshGroupParam)
220         else:
221             for item in meshGroupParam:
222                 meshGroupList += self._getMeshGroupListFromString(item)
223         
224         if len(meshGroupList) == 0:
225             logger.warning("Mesh group list is empty in command %s, this "
226                            "command will be ignored." % command)
227
228         return (meshGroupList, newparams)
229     
230     def _getMeshGroupListFromString(self, meshString):
231         """
232         This method splits the string in parameter to extract comma separated
233         names. Those names are returned as a list of strings.
234         """
235         meshGroupList = []
236         list = meshString.split(",")
237         for item in list:
238             strippedItem = item.strip()
239             if len(strippedItem) > 0:
240                 meshGroupList.append(strippedItem)
241         return meshGroupList
242
243
244 class StructuralElement:
245     """
246     This class represents a structural element, i.e. a set of geometrical
247     objects built along geometrical primitives. The parameter `studyId`
248     defines the ID of the study that will contain the structural element. If
249     it is :const:`None` or not specified, the constructor will use the ID of
250     the active study as defined by :func:`salome.kernel.studyedit.getActiveStudyId`
251     function. Structural elements are normally created by the class
252     :class:`StructuralElementManager`, so this class should not be
253     instantiated directly in the general case.
254     """
255     _counter = 1
256     _mainFolderTag = 14725
257
258     def __init__(self, studyId = None):
259         # _parts is the dictionary mapping group name to structural element
260         # part. _shapeDict is the dictionary mapping SubShapeID objects to
261         # structural element parts. Both are used to avoid duplicate shapes
262         # in structural elements.
263         self._parts = {}
264         self._shapeDict = {}
265         self._id = StructuralElement._counter
266         StructuralElement._counter += 1
267         self._studyEditor = getStudyEditor(studyId)
268         logger.debug("Creating structural element in study %s" %
269                      self._studyEditor.studyId)
270         self._SObject = None
271
272     def _getSObject(self):
273         """
274         Find or create the study object corresponding to the structural
275         element. This object is named "SE_N" where N is a numerical ID. 
276         """
277         if self._SObject is None:
278             geomComponent = self._studyEditor.study.FindComponent("GEOM")
279             mainFolder = self._studyEditor.setItemAtTag(geomComponent,
280                                             StructuralElement._mainFolderTag,
281                                             name = "Structural Elements")
282             self._SObject = self._studyEditor.findOrCreateItem(mainFolder,
283                                             name = "SE_" + str(self._id))
284         return self._SObject
285
286     def addPart(self, newpart):
287         """
288         Add a part to the structural element.
289
290         :type  newpart: :class:`~parts.StructuralElementPart`
291         :param newpart: the part to add to the structural element.
292
293         """
294         newshapes = newpart.baseShapesSet
295
296         # Check duplicate groups
297         if self._parts.has_key(newpart.groupName):
298             logger.warning('Mesh group "%s" is used several times in the '
299                            'structural element. Only the last definition '
300                            'will be used.' % newpart.groupName)
301         else:
302             # Check duplicate shapes
303             intersect = newshapes.intersection(self._shapeDict.keys())
304             while len(intersect) > 0:
305                 shape, = intersect
306                 oldpartwithshape = self._shapeDict[shape]
307                 oldpartshapes = oldpartwithshape.baseShapesSet
308                 intersectwitholdpart = intersect.intersection(oldpartshapes)
309                 logger.warning('Some shapes are common to groups "%s" and '
310                                '"%s". For those, the parameters defined for '
311                                '"%s" will be used.' %
312                                (oldpartwithshape.groupName, newpart.groupName,
313                                 newpart.groupName))
314                 oldpartwithshape.baseShapesSet = \
315                                 oldpartshapes.difference(intersectwitholdpart)
316                 intersect = intersect.difference(intersectwitholdpart)
317
318         # Finally add the new part in the structural element
319         self._parts[newpart.groupName] = newpart
320         for shape in newshapes:
321             self._shapeDict[shape] = newpart
322
323     def addOrientation(self, meshGroup, orientParams):
324         """
325         Add orientation information to a part in the structural element. This
326         information will be used to build the corresponding markers.
327
328         :type  meshGroup: string
329         :param meshGroup: the name of a geometrical primitive. The orientation
330                           information will apply to the structural element
331                           part built along this primitive.
332
333         :type  orientParams: dictionary
334         :param orientParams: parameters defining the orientation of the
335                              structural element part. Those parameters are
336                              detailed in class
337                              :class:`~orientation.Orientation1D`.
338
339         """
340         if self._parts.has_key(meshGroup):
341             self._parts[meshGroup].addOrientation(orientParams)
342         else:
343             logger.warning('Mesh group "%s" not found in structural element, '
344                            'cannot set orientation.' % meshGroup)
345
346     def build(self):
347         """
348         Build the geometric shapes and the markers corresponding to the
349         different parts of the structural element, and add them to the study.
350         """
351         gg = salome.ImportComponentGUI("GEOM")
352         for part in self._parts.itervalues():
353             # Build the structural element part
354             logger.debug("Building %s" % part)
355             try:
356                 (shape, markers) = part.build()
357                 if shape is None:
358                     logger.error("Part %s has not been built" % part)
359                     continue
360             except:
361                 logger.exception("Couldn't build part %s" % part)
362                 continue
363             
364             # Add the new objects to the study
365             IOR = self._studyEditor.study.ConvertObjectToIOR(shape)
366             shapeSObjName = part.name + "_" + part.groupName
367             icon = None
368             if salome.hasDesktop():
369                 icon = gg.getShapeTypeIcon(IOR)
370             shapeSObj = self._studyEditor.createItem(self._getSObject(),
371                                             name = shapeSObjName, IOR = IOR,
372                                             icon = icon)
373             if markers is not None and len(markers) > 0:
374                 i = 1
375                 for marker in markers:
376                     markerIOR = \
377                             self._studyEditor.study.ConvertObjectToIOR(marker)
378                     markerSObjName = "Orient_" + shapeSObjName
379                     if len(markers) > 1:
380                         markerSObjName += "_%d" % i
381                     markerSObj = self._studyEditor.createItem(
382                                                 self._getSObject(),
383                                                 name = markerSObjName,
384                                                 IOR = markerIOR,
385                                                 icon = "ICON_OBJBROWSER_LCS")
386                     i += 1
387
388     def display(self):
389         """
390         Display the structural element in the geom view.
391         """
392         StructuralElement.showElement(self._SObject)
393
394     @staticmethod
395     def showElement(theSObject):
396         """
397         Display the structural element corresponding to the study object
398         `theSObject`
399         """
400         if theSObject is not None:
401             gg = salome.ImportComponentGUI("GEOM")
402             aStudy = theSObject.GetStudy()
403             editor = getStudyEditor(aStudy._get_StudyId())
404             aIterator = aStudy.NewChildIterator(theSObject)
405             aIterator.Init()
406             while aIterator.More():
407                 sobj = aIterator.Value()
408                 icon = editor.getIcon(sobj)
409                 if icon != "ICON_OBJBROWSER_LCS":
410                     entry = aIterator.Value().GetID()
411                     gg.createAndDisplayGO(entry)
412                     gg.setDisplayMode(entry, 1)
413                 aIterator.Next()
414
415
416 def TEST_CreateGeometry():
417     import salome
418     salome.salome_init()
419     import GEOM
420     from salome.geom import geomBuilder
421     geompy = geomBuilder.New(salome.myStudy)
422     import SALOMEDS
423     geompy.init_geom(salome.myStudy)
424     Box_1 = geompy.MakeBoxDXDYDZ(200, 200, 200)
425     edges = geompy.SubShapeAllSorted(Box_1, geompy.ShapeType["EDGE"])
426     geompy.addToStudy(Box_1, "Box_1")
427     for i in range(len(edges)):
428         geompy.addToStudyInFather(Box_1, edges[i], "Edge_%d" % i)
429     faces = geompy.SubShapeAllSorted(Box_1, geompy.ShapeType["FACE"])
430     faces[3].SetColor(SALOMEDS.Color(1.0,0.5,0.0))
431     faces[4].SetColor(SALOMEDS.Color(0.0,1.0,0.5))
432     for i in range(len(faces)):
433         geompy.addToStudyInFather(Box_1, faces[i], "Face_%d" % i)
434     Cylinder_1 = geompy.MakeCylinderRH(50, 200)
435     geompy.TranslateDXDYDZ(Cylinder_1, 300, 300, 0)
436     cyl_faces = geompy.SubShapeAllSorted(Cylinder_1, geompy.ShapeType["FACE"])
437     geompy.addToStudy(Cylinder_1, "Cylinder_1")
438     for i in range(len(cyl_faces)):
439         geompy.addToStudyInFather(Cylinder_1, cyl_faces[i], "CylFace_%d" % i)
440     Cylinder_2 = geompy.MakeTranslation(Cylinder_1, 100, 100, 0)
441     cyl_faces2 = geompy.SubShapeAllSorted(Cylinder_2,
442                                           geompy.ShapeType["FACE"])
443     geompy.addToStudy(Cylinder_2, "Cylinder_2")
444     for i in range(len(cyl_faces2)):
445         geompy.addToStudyInFather(Cylinder_2, cyl_faces2[i],
446                                   "CylFace2_%d" % i)
447
448
449 def TEST_StructuralElement():
450     salome.salome_init()
451     TEST_CreateGeometry()
452     liste_commandes = [('Orientation', {'MeshGroups': 'Edge_4',
453                                         'VECT_Y': (1.0, 0.0, 1.0)}),
454                        ('Orientation', {'MeshGroups': 'Edge_5',
455                                         'ANGL_VRIL': 45.0}),
456                        ('GeneralBeam', {'MeshGroups': 'Edge_1, Edge_7',
457                                         'A': 1, 'IY1': 20, 'IY2': 40,
458                                         'IZ1': 60, 'IZ2': 30}),
459                        ('VisuPoutreCercle', {'MeshGroups': ['Edge_6'],
460                                              'R1': 30, 'R2': 20}),
461                        ('CircularBeam', {'MeshGroups': ['Edge_2', 'Edge_3'],
462                                          'R': 40, 'EP': 20}),
463                        ('RectangularBeam', {'MeshGroups': 'Edge_4, Edge_5',
464                                             'HZ1': 60, 'HY1': 40,
465                                             'EPZ1': 15, 'EPY1': 10,
466                                             'HZ2': 40, 'HY2': 60,
467                                             'EPZ2': 10, 'EPY2': 15}),
468                        ('VisuCable', {'MeshGroups': 'Edge_7', 'R': 5}),
469                        ('VisuCoque', {'MeshGroups': 'Face_4',
470                                       'Epais': 10, 'Excentre': 5,
471                                       'angleAlpha': 45, 'angleBeta': 60}),
472                        ('VisuCoque', {'MeshGroups': 'CylFace_2', 'Epais': 5}),
473                        ('VisuGrille', {'MeshGroups': 'Face_5', 'Excentre': 5,
474                                        'angleAlpha': 45, 'angleBeta': 60}),
475                        ('VisuGrille', {'MeshGroups': 'CylFace2_2',
476                                        'Excentre': 5, 'origAxeX': 400,
477                                        'origAxeY': 400, 'origAxeZ': 0,
478                                        'axeX': 0, 'axeY': 0, 'axeZ': 100}),
479                       ]
480
481     structElemManager = StructuralElementManager()
482     elem = structElemManager.createElement(liste_commandes)
483     if salome.hasDesktop():
484         elem.display()
485         salome.sg.updateObjBrowser(True)
486
487
488 # Main function only used to test the module
489 if __name__ == "__main__":
490     TEST_StructuralElement()