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