1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2007-2019 CEA/DEN, EDF R&D, OPEN CASCADE
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.
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.
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
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 ## \defgroup structelem structelem - Structural elements package
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.
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
41 # \n Structural elements are generally created by the StructuralElementManager class,
42 # from a list of commands describing the element to create.
46 # commandList = [('VisuPoutreGenerale', {'Group_Maille': 'Edge_1'}),
47 # ('VisuBarreCercle', {'R': 30, 'Group_Maille': 'Edge_1', 'EP': 15}),
50 # structElemManager = StructuralElementManager()
51 # elem = structElemManager.createElement(commandList)
53 # salome.sg.updateObjBrowser()
56 # \defgroup orientation
61 This package is used to create and visualize structural elements. It contains
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.
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
79 Structural elements are generally created by the
80 :class:`StructuralElementManager` class, from a list of commands describing
81 the element to create.
85 commandList = [('VisuPoutreGenerale', {'Group_Maille': 'Edge_1'}),
87 {'R': 30, 'Group_Maille': 'Edge_1', 'EP': 15}),
90 structElemManager = StructuralElementManager()
91 elem = structElemManager.createElement(commandList)
93 salome.sg.updateObjBrowser()
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
106 __all__ = ["parts", "orientation"]
108 from salome.geom.geomtools import getGeompy
109 from salome.geom.structelem import parts
110 from salome.geom.structelem.parts import InvalidParameterError
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:
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.
123 self._studyEditor = getStudyEditor()
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
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.
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):
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:
174 * :class:`~parts.GeneralBeam`
175 * :class:`~parts.CircularBeam`
176 * :class:`~parts.RectangularBeam`
177 * :class:`~parts.ThickShell`
178 * :class:`~parts.Grid`
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`)
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`.
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.
210 logger.debug("StructuralElementManager.createElement: START")
211 logger.debug("Command list: %s" % commandList)
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))
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]
232 groupSObj = self._studyEditor.study.FindObject(meshGroup)
235 if groupSObj is not None:
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))
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)
254 logger.exception("Can't create structural element"
255 " part with command %s." %
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)
267 logger.debug("StructuralElementManager.createElement: END")
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
275 def _extractMeshGroups(self, command):
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
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.' %
295 elif groupMailleParam is not None and meshGroupParam is None:
296 meshGroupParam = groupMailleParam
298 if isinstance(meshGroupParam, str):
299 meshGroupList = [meshGroupParam]
301 meshGroupList = meshGroupParam
303 if len(meshGroupList) == 0:
304 logger.warning("Mesh group list is empty in command %s, this "
305 "command will be ignored." % command)
307 return (meshGroupList, newparams)
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:
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.
324 MAIN_FOLDER_NAME = "Structural Elements"
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.
333 self._id = StructuralElement._counter
334 StructuralElement._counter += 1
335 self._studyEditor = getStudyEditor()
336 logger.debug("Creating structural element in study")
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
342 def _getSObject(self):
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
348 if self._SObject is None:
350 geomComponent = self._studyEditor.study.FindComponent("GEOM")
351 mainFolder = self._studyEditor.findItem(geomComponent,
352 name = StructuralElement.MAIN_FOLDER_NAME,
354 if mainFolder is None:
355 mainFolder = geompy.NewFolder(StructuralElement.MAIN_FOLDER_NAME)
356 self._SObject = geompy.NewFolder("SE_" + str(self._id), mainFolder)
359 ## Add a part to the structural element.
361 # \param newpart (StructuralElementPart) the part to add to the structural element.
362 def addPart(self, newpart):
364 Add a part to the structural element.
366 :type newpart: :class:`~parts.StructuralElementPart`
367 :param newpart: the part to add to the structural element.
370 newshapes = newpart.baseShapesSet
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)
378 # Check duplicate shapes
379 intersect = newshapes.intersection(list(self._shapeDict.keys()))
380 while len(intersect) > 0:
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,
390 oldpartwithshape.baseShapesSet = \
391 oldpartshapes.difference(intersectwitholdpart)
392 intersect = intersect.difference(intersectwitholdpart)
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
399 ## Add orientation information to a part in the structural element. This
400 # information will be used to build the corresponding markers.
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):
408 Add orientation information to a part in the structural element. This
409 information will be used to build the corresponding markers.
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.
416 :type orientParams: dictionary
417 :param orientParams: parameters defining the orientation of the
418 structural element part. Those parameters are
420 :class:`~orientation.Orientation1D`.
423 if meshGroup in self._parts:
424 self._parts[meshGroup].addOrientation(orientParams)
426 logger.warning('Mesh group "%s" not found in structural element, '
427 'cannot set orientation.' % meshGroup)
429 ## Build the geometric shapes and the markers corresponding to the
430 # different parts of the structural element, and add them to the study.
433 Build the geometric shapes and the markers corresponding to the
434 different parts of the structural element, and add them to the study.
436 gg = salome.ImportComponentGUI("GEOM")
439 for part in self._parts.values():
440 # Build the structural element part
441 logger.debug("Building %s" % part)
443 (shape, markers) = part.build()
445 logger.error("Part %s has not been built" % part)
448 logger.exception("Couldn't build part %s" % part)
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())
456 if markers is not None and len(markers) > 0:
457 for i, marker in enumerate(markers, start = 1):
458 markerSObjName = "Orient_" + shapeSObjName
460 markerSObjName += "_%d" % i
461 geompy.addToStudy(marker, markerSObjName)
462 geompy.PutToFolder(marker, self._getSObject())
464 ## Display the structural element in the geom view.
467 Display the structural element in the geom view.
469 StructuralElement.showElement(self._SObject)
472 ## Display the structural element corresponding to the study object \b theSObject
473 def showElement(theSObject):
475 Display the structural element corresponding to the study object
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
494 def TEST_CreateGeometry():
498 from salome.geom import geomBuilder
499 geompy = geomBuilder.New()
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],
527 def TEST_StructuralElement():
529 TEST_CreateGeometry()
530 liste_commandes = [('Orientation', {'MeshGroups': 'Edge_4',
531 'VECT_Y': (1.0, 0.0, 1.0)}),
532 ('Orientation', {'MeshGroups': 'Edge_5',
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'],
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}),
559 structElemManager = StructuralElementManager()
560 elem = structElemManager.createElement(liste_commandes)
561 if salome.hasDesktop():
563 salome.sg.updateObjBrowser()
566 # Main function only used to test the module
567 if __name__ == "__main__":
568 TEST_StructuralElement()