Salome HOME
updated copyright message
[modules/kernel.git] / src / KERNEL_PY / kernel / studyedit.py
1 # Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 ## \defgroup studyedit studyedit
21 #  \{ 
22 #  \details
23 #  This module provides a new class \bStudyEditor to complement \bStudy
24 #  and \bStudyBuilder classes.
25 #  \}
26
27 """
28 This module provides a new class :class:`StudyEditor` to complement
29 :class:`Study` and :class:`StudyBuilder` classes.
30 """
31
32 import re
33
34 from salome.kernel import termcolor
35 from salome.kernel.logger import Logger
36
37
38 logger = Logger("salome.kernel.studyedit", color=termcolor.PURPLE)
39
40 _DEFAULT_CONTAINER = "FactoryServer"
41
42
43 ## This class provides utility methods to complement \b Study and
44 #  \b StudyBuilder classes. Those methods may be moved in those classes
45 #  in the future.
46 #  The preferred way to get a StudyEditor object is through the method
47 #  \b getStudyEditor which allows to reuse existing instance or through
48 #  the global module attribute \b EDITOR.
49 #
50 #  \param lcc This instance attribute provides access to the SALOME life cycle
51 #  CORBA service.
52 #
53 #  \param study This instance attribute provides access to the \b Study object.
54 #  It can be used to access the study but the attribute itself cannot
55 #  be modified.
56 #
57 #  \param builder This instance attribute provides access to the \b StudyBuilder
58 #  object. It can be used to edit the study but the attribute itself
59 #  cannot be modified.
60 #  \ingroup studyedit
61 class StudyEditor:
62     """
63     This class provides utility methods to complement :class:`Study` and
64     :class:`StudyBuilder` classes. Those methods may be moved in those classes
65     in the future.
66     The preferred way to get a StudyEditor object is through the method
67     :meth:`getStudyEditor` which allows to reuse existing instances.
68
69     .. attribute:: lcc
70     
71        This instance attribute provides access to the SALOME life cycle
72        CORBA service.
73
74     .. attribute:: study
75     
76        This instance attribute provides access to the :class:`Study` object.
77        It can be used to access the study but the attribute itself cannot
78        be modified.
79
80     .. attribute:: builder
81
82        This instance attribute provides access to the :class:`StudyBuilder`
83        object. It can be used to edit the study but the attribute itself
84        cannot be modified.
85
86     """
87
88     @property
89     def study(self):
90         """Attribute that provides access to the study."""
91         import salome
92         salome.salome_init()
93         if salome.myStudy is None:
94             raise Exception("Study doesn't exist")
95         return salome.myStudy
96
97     @property
98     def builder(self):
99         """Attribute that provides access to the study builder."""
100         import salome
101         salome.salome_init()
102         if salome.myStudy is None:
103             raise Exception("Study doesn't exist")
104         return salome.myStudy.NewBuilder()
105
106     @property
107     def lcc(self):
108         """Attribute that providess access to the SALOME life cycle CORBA service."""
109         import salome
110         salome.salome_init()
111         return salome.lcc
112
113     ## Find a component corresponding to the Salome module \b moduleName in
114     #  the study. If none is found, create a new component and associate it
115     #  with the corresponding engine (i.e. the engine named \b moduleName).
116     #  Note that in Salome 5, the module name and engine name must be
117     #  identical (every module must provide an engine with the same name).
118     #  In Salome 6 it will be possible to define a different name for the
119     #  engine.
120     #
121     #  \param moduleName (string) name of the module corresponding to the component
122     #  (the module name is the string value in the
123     #  attribute "AttributeComment" of the component)
124     #
125     #  \param componentName (string) name of the new component if created. 
126     #  If \b None, use \b moduleName instead.
127     #
128     #  \param icon (string) icon for the new component (attribute "AttributePixMap").
129     #
130     #  \param containerName (string) name of the container in which the engine should be
131     #  loaded.
132     #
133     #  \return the SComponent found or created.
134     def findOrCreateComponent(self, moduleName, componentName = None,
135                               icon = None, containerName = _DEFAULT_CONTAINER):
136         """
137         Find a component corresponding to the Salome module `moduleName` in
138         the study. If none is found, create a new component and associate it
139         with the corresponding engine (i.e. the engine named `moduleName`).
140         Note that in Salome 5, the module name and engine name must be
141         identical (every module must provide an engine with the same name).
142         In Salome 6 it will be possible to define a different name for the
143         engine.
144
145         :type  moduleName: string
146         :param moduleName: name of the module corresponding to the component
147                            (the module name is the string value in the
148                            attribute "AttributeComment" of the component)
149
150         :type  componentName: string
151         :param componentName: name of the new component if created. If
152                               :const:`None`, use `moduleName` instead.
153
154         :type  icon: string
155         :param icon: icon for the new component (attribute "AttributePixMap").
156
157         :type  containerName: string
158         :param containerName: name of the container in which the engine should be
159                               loaded.
160
161         :return: the SComponent found or created.
162
163         """
164         sComponent = self.study.FindComponent(moduleName)
165         if sComponent is None:
166             sComponent = self.builder.NewComponent(moduleName)
167             # Note that the NewComponent method set the "comment" attribute to the
168             # value of its argument (moduleName here)
169             if componentName is None:
170                 componentName = moduleName
171             self.builder.SetName(sComponent, componentName)
172             if icon is not None:
173                 # _MEM_ : This will be effective if and only if "moduleName"
174                 # really corresponds to the module name (as specified in the
175                 # SalomeApp.xml)
176                 self.setIcon(sComponent, icon)
177
178             # This part will stay inactive until Salome 6. In Salome 6, the
179             # engine name will be stored separately from the module name.
180             # An internal convention (in this class) is to store the name of the
181             # associated engine in the parameter attribute of the scomponent (so that
182             # it could be retrieved in a future usage of this scomponent, for example,
183             # for the need of the function loadComponentEngine). The comment attribute
184             # SHOULD NOT be used for this purpose  because it's used by the SALOME
185             # resources manager to identify the SALOME module and then localized
186             # the resource files
187             #attr = self.builder.FindOrCreateAttribute( sComponent, "AttributeParameter" )
188             #attr.SetString( "ENGINE_NAME", engineName )
189
190             engine = self.lcc.FindOrLoadComponent(containerName, moduleName)
191             if engine is None:
192                 raise Exception("Cannot load engine %s in container %s. See "
193                                 "logs of container %s for more details." %
194                                 (moduleName, containerName, containerName))
195             self.builder.DefineComponentInstance(sComponent, engine)
196
197         return sComponent
198
199     ## Load the engine corresponding to \b sComponent in the container
200     #  \b containerName, associate the engine with the component and load the
201     #  CORBA objects of this component in the study.
202     def loadComponentEngine(self, sComponent,
203                             containerName = _DEFAULT_CONTAINER):
204         """
205         Load the engine corresponding to `sComponent` in the container
206         `containerName`, associate the engine with the component and load the
207         CORBA objects of this component in the study.
208         """
209         # This part will stay inactive until Salome 6. In Salome 6, the
210         # engine name will be stored separately from the module name.
211         #attr = self.builder.FindOrCreateAttribute( sComponent, "AttributeParameter" )
212         #engineName = attr.GetString( "ENGINE_NAME" )
213         engine = self.lcc.FindOrLoadComponent(containerName,
214                                               sComponent.GetComment())
215         if engine is None:
216             raise Exception("Cannot load component %s in container %s. See "
217                             "logs of container %s for more details." %
218                             (sComponent.GetComment(), containerName,
219                              containerName))
220         self.builder.LoadWith(sComponent, engine)
221
222     ## Get the CORBA object associated with the SObject \b item, eventually by
223     #  first loading it with the corresponding engine.
224     def getOrLoadObject(self, item):
225         """
226         Get the CORBA object associated with the SObject `item`, eventually by
227         first loading it with the corresponding engine.
228         """
229         object = item.GetObject()
230         if object is None: # the engine has not been loaded yet
231             sComponent = item.GetFatherComponent()
232             self.loadComponentEngine(sComponent)
233             object = item.GetObject()
234         return object
235
236     ## Find an object under \b fatherItem in the study with the given
237     #  attributes. Return the first one found if at least one exists,
238     #  otherwise create a new one with the given attributes and return it.
239     #
240     #  See \b setItem() for the description of the parameters.
241     def findOrCreateItem(self, fatherItem, name, fileType = None,
242                          fileName = None, comment = None, icon = None,
243                          IOR = None, typeId = None):
244         """
245         Find an object under `fatherItem` in the study with the given
246         attributes. Return the first one found if at least one exists,
247         otherwise create a new one with the given attributes and return it.
248         
249         See :meth:`setItem` for the description of the parameters.
250         """
251         sObject = self.findItem(fatherItem, name, fileType, fileName, comment,
252                                 icon, IOR, typeId)
253         if sObject is None:
254             sObject = self.createItem(fatherItem, name, fileType, fileName,
255                                       comment, icon, IOR, typeId)
256         return sObject
257
258     ## Find an item with given attributes under \b fatherItem in the study. If
259     #  none is found, return \b None. If several items correspond to
260     #  the parameters, only the first one is returned. The search is made
261     #  only on given parameters (i.e. not \b None). To look explicitly
262     #  for an empty attribute, use an empty string in the corresponding
263     #  parameter.
264     #    
265     #  See \b setItem() for the description of the parameters.
266     def findItem(self, fatherItem, name = None, fileType = None,
267                  fileName = None, comment = None, icon = None, IOR = None,
268                  typeId = None):
269         """
270         Find an item with given attributes under `fatherItem` in the study. If
271         none is found, return :const:`None`. If several items correspond to
272         the parameters, only the first one is returned. The search is made
273         only on given parameters (i.e. not :const:`None`). To look explicitly
274         for an empty attribute, use an empty string in the corresponding
275         parameter.
276         
277         See :meth:`setItem` for the description of the parameters.
278         """
279         foundItem = None;
280         childIterator = self.study.NewChildIterator(fatherItem)
281         while childIterator.More() and foundItem is None:
282             childItem = childIterator.Value()
283             if childItem and \
284                (name is None or self.getName(childItem) == name) and \
285                (fileType is None or \
286                 self.getFileType(childItem) == fileType) and \
287                (fileName is None or \
288                 self.getFileName(childItem) == fileName) and \
289                (comment is None or self.getComment(childItem) == comment) and \
290                (icon is None or \
291                 self.getIcon(childItem) == icon) and \
292                (IOR is None or childItem.GetIOR() == IOR) and \
293                (typeId is None or \
294                 self.getTypeId(childItem) == typeId):
295                 foundItem = childItem
296             childIterator.Next()
297         return foundItem
298
299     ## Create a new object named \b name under \b fatherItem in the study, with
300     #  the given attributes. If an object named \b name already exists under
301     #  the father object, the new object is created with a new name \b name_X
302     #  where X is the first available index.
303     #
304     #  param fatherItem (SObject) item under which the new item will be added.
305     #  \return new SObject created in the study.
306     #
307     #  See \b setItem() for the description of the other parameters.
308     def createItem(self, fatherItem, name, fileType=None, fileName=None,
309                    comment=None, icon=None, IOR=None, typeId=None):
310         """
311         Create a new object named `name` under `fatherItem` in the study, with
312         the given attributes. If an object named `name` already exists under
313         the father object, the new object is created with a new name `name_X`
314         where X is the first available index.
315
316         :type  fatherItem: SObject
317         :param fatherItem: item under which the new item will be added.
318
319         :return: new SObject created in the study
320
321         See :meth:`setItem` for the description of the other parameters.
322         """
323         aSObject = self.builder.NewObject(fatherItem)
324
325         aChildIterator = self.study.NewChildIterator(fatherItem)
326         aMaxId = -1
327         aLength = len(name)
328         aDelim = "_"
329         anIdRE = re.compile("^" + aDelim + "[0-9]+")
330         aNameRE = re.compile("^" + name)
331         while aChildIterator.More():
332             aSObj = aChildIterator.Value()
333             aChildIterator.Next()
334             aName = aSObj.GetName()
335             if re.match(aNameRE, aName):
336                 aTmp = aName[aLength:]
337                 if re.match(anIdRE, aTmp):
338                     anId = int(aTmp[1:])
339                     if aMaxId < anId:
340                         aMaxId = anId
341                         pass
342                     pass
343                 elif aMaxId < 0:
344                     aMaxId = 0
345                     pass
346                 pass
347             pass
348
349         aMaxId = aMaxId + 1
350         aName = name
351         if aMaxId > 0:
352             aName = aName + aDelim + str(aMaxId)
353             pass
354
355         self.setItem(aSObject, aName, fileType, fileName, comment, icon,
356                      IOR, typeId)
357
358         return aSObject
359
360     ## Modify the attributes of an item in the study. Unspecified attributes
361     #  (i.e. those set to \b None) are left unchanged.
362     #
363     #  \param item (SObject) item to modify.
364     #
365     #  \param name (string or unicode) item name (attribute \b AttributeName).
366     #
367     #  \param fileType (string or unicode) item file type (attribute \b AttributeFileType).
368     #
369     #  \param fileName (string or unicode) item file name (attribute \b AttributeExternalFileDef).
370     #
371     #  \param comment (string or unicode) item comment (attribute \b AttributeComment). Note that
372     #  this attribute will appear in the \b Value column in the object browser.
373     #
374     #  \param icon (string or unicode) item icon name (attribute \b AttributePixMap).
375     #
376     #  \param IOR (string) IOR of a CORBA object associated with the item
377     #  (attribute \b AttributeIOR).
378     #
379     #  \param typeId (integer) item type (attribute \b AttributeLocalID).
380     def setItem(self, item, name = None, fileType = None, fileName = None,
381                 comment = None, icon = None, IOR = None, typeId = None):
382         """
383         Modify the attributes of an item in the study. Unspecified attributes
384         (i.e. those set to :const:`None`) are left unchanged.
385
386         :type  item: SObject
387         :param item: item to modify.
388
389         :type  name: string or unicode
390         :param name: item name (attribute 'AttributeName').
391
392         :type  fileType: string or unicode
393         :param fileType: item file type (attribute 'AttributeFileType').
394
395         :type  fileName: string or unicode
396         :param fileName: item file name (attribute
397                          'AttributeExternalFileDef').
398
399         :type  comment: string or unicode
400         :param comment: item comment (attribute 'AttributeComment'). Note that
401                         this attribute will appear in the 'Value' column in
402                         the object browser.
403
404         :type  icon: string or unicode
405         :param icon: item icon name (attribute 'AttributePixMap').
406
407         :type  IOR: string
408         :param IOR: IOR of a CORBA object associated with the item
409                     (attribute 'AttributeIOR').
410
411         :type  typeId: integer
412         :param typeId: item type (attribute 'AttributeLocalID').
413         """
414         logger.debug("setItem (ID=%s): name=%s, fileType=%s, fileName=%s, "
415                      "comment=%s, icon=%s, IOR=%s" %
416                      (item.GetID(), name, fileType, fileName, comment,
417                       icon, IOR))
418         if name is not None:
419             self.setName(item, name)
420         if fileType is not None:
421             self.setFileType(item, fileType)
422         if fileName is not None:
423             self.setFileName(item, fileName)
424         if comment is not None:
425             self.setComment(item, comment)
426         if icon is not None:
427             self.setIcon(item, icon)
428         if IOR is not None:
429             self.builder.SetIOR(item, IOR)
430         if typeId is not None:
431             self.setTypeId(item, typeId)
432
433     ## Remove the given item from the study. Note that the items are never
434     #  really deleted. They just don't appear in the study anymore.
435     #
436     #  \param item (SObject) the item to be removed
437     #
438     #  \param withChildren (boolean) if \b True, also remove the children of item
439     #
440     #  \return \b True if the item was removed successfully, or 
441     #  \b False if an error happened.
442     def removeItem(self, item, withChildren = False ):
443         """
444         Remove the given item from the study. Note that the items are never
445         really deleted. They just don't appear in the study anymore.
446
447         :type  item: SObject
448         :param item: the item to be removed
449
450         :type  withChildren: boolean
451         :param withChildren: if :const:`True`, also remove the children of
452                              `item`
453
454         :return: :const:`True` if the item was removed successfully, or
455                  :const:`False` if an error happened.
456         """
457         ok = False
458         try:
459             if withChildren:
460                 self.builder.RemoveObjectWithChildren(item)
461             else:
462                 self.builder.RemoveObject(item)
463             ok = True
464         except Exception:
465             ok = False
466         return ok
467
468     ## Find an item tagged \b tag under \b fatherItem in the study tree or
469     #  create it if there is none, then set its attributes.
470     #
471     #  \param fatherItem (SObject) item under which the tagged item will be looked for
472     #  and eventually created.
473     #
474     #  \param tag integer) tag of the item to look for.
475     #
476     #  \return the SObject at \b tag if found or created successfully, or
477     #  \b None if an error happened.
478     #    
479     #  See \b setItem() for the description of the other parameters.
480     def setItemAtTag(self, fatherItem, tag, name = None, fileType = None,
481                      fileName = None, comment = None, icon = None, IOR = None,
482                      typeId = None):
483         """
484         Find an item tagged `tag` under `fatherItem` in the study tree or
485         create it if there is none, then set its attributes.
486         
487         :type  fatherItem: SObject
488         :param fatherItem: item under which the tagged item will be looked for
489                            and eventually created.
490
491         :type  tag: integer
492         :param tag: tag of the item to look for.
493
494         :return: the SObject at `tag` if found or created successfully, or
495                  :const:`None` if an error happened.
496         
497         See :meth:`setItem` for the description of the other parameters.
498         """
499         found, sObj = fatherItem.FindSubObject(tag)
500         if not found:
501             sObj = self.builder.NewObjectToTag(fatherItem, tag)
502         self.setItem(sObj, name, fileType, fileName, comment, icon,
503                      IOR, typeId)
504         return sObj
505
506     ## Return the name of the object sObject
507     def getName(self, sObject):
508         return sObject.GetName()
509
510     ## Set the name of the object sObject
511     def setName(self, sObject, name):
512         self.builder.SetName(sObject, name)
513
514     ## Return the comment of the object sObject
515     def getComment(self, sObject):
516         return sObject.GetComment()
517
518     ## Set the comment of the object sObject
519     def setComment(self, sObject, comment):
520         self.builder.SetComment(sObject, comment)
521
522     ## Return the value of the attribute named \b attributeName on the object
523     #  sObject, or \b default if the attribute doesn't exist.
524     def getAttributeValue(self, sObject, attributeName, default = None):
525         """
526         Return the value of the attribute named `attributeName` on the object
527         `sObject`, or `default` if the attribute doesn't exist.
528         """
529         value = default
530         found, attr = self.builder.FindAttribute(sObject, attributeName)
531         if found:
532             value = attr.Value()
533         return value
534
535     ## Set the value of the attribute named \b attributeName on the object
536     #  sObject to the value \b attributeValue.
537     def setAttributeValue(self, sObject, attributeName, attributeValue):
538         """
539         Set the value of the attribute named `attributeName` on the object
540         `sObject` to the value `attributeValue`.
541         """        
542         attr = self.builder.FindOrCreateAttribute(sObject, attributeName)
543         attr.SetValue(attributeValue)
544
545     ## Return the value of the attribute "AttributeLocalID" of the object
546     #  sObject, or \b None if it is not set.
547     def getTypeId(self, sObject):
548         """
549         Return the value of the attribute "AttributeLocalID" of the object
550         `sObject`, or :const:`None` if it is not set.
551         """
552         return self.getAttributeValue(sObject, "AttributeLocalID")
553
554     ## Set the attribute "AttributeLocalID" of the object \b sObject to the
555     #  value \b value.
556     def setTypeId(self, sObject, value):
557         """
558         Set the attribute "AttributeLocalID" of the object `sObject` to the
559         value `value`.
560         """
561         self.setAttributeValue(sObject, "AttributeLocalID", value)
562
563     ## Return the value of the attribute "AttributeFileType" of the object
564     #  sObject, or an empty string if it is not set.
565     def getFileType(self, sObject):
566         """
567         Return the value of the attribute "AttributeFileType" of the object
568         `sObject`, or an empty string if it is not set.
569         """
570         return self.getAttributeValue(sObject, "AttributeFileType", "")
571
572     ## Set the attribute "AttributeFileType" of the object sObject to the
573     #  value value.
574     def setFileType(self, sObject, value):
575         """
576         Set the attribute "AttributeFileType" of the object `sObject` to the
577         value `value`.
578         """
579         self.setAttributeValue(sObject, "AttributeFileType",
580                                value)
581
582     ## Return the value of the attribute "AttributeExternalFileDef" of the
583     #  object sObject, or an empty string if it is not set.
584     def getFileName(self, sObject):
585         """
586         Return the value of the attribute "AttributeExternalFileDef" of the
587         object `sObject`, or an empty string if it is not set.
588         """
589         return self.getAttributeValue(sObject, "AttributeExternalFileDef", "")
590
591     ## Set the attribute "AttributeExternalFileDef" of the object sObject
592     #  to the value value.
593     def setFileName(self, sObject, value):
594         """
595         Set the attribute "AttributeExternalFileDef" of the object `sObject`
596         to the value `value`.
597         """
598         self.setAttributeValue(sObject, "AttributeExternalFileDef",
599                                value)
600
601     ## Return the value of the attribute "AttributePixMap" of the object
602     #  sObject, or an empty string if it is not set.
603     def getIcon(self, sObject):
604         """
605         Return the value of the attribute "AttributePixMap" of the object
606         `sObject`, or an empty string if it is not set.
607         """
608         value = ""
609         found, attr = self.builder.FindAttribute(sObject, "AttributePixMap")
610         if found and attr.HasPixMap():
611             value = attr.GetPixMap()
612         return value
613
614     ## Set the attribute "AttributePixMap" of the object sObject to the
615     #  value value.
616     def setIcon(self, sObject, value):
617         """
618         Set the attribute "AttributePixMap" of the object `sObject` to the
619         value `value`.
620         """
621         attr = self.builder.FindOrCreateAttribute(sObject, "AttributePixMap")
622         attr.SetPixMap(value)
623
624 ## Singleton study editor instance.
625 #  \ingroup studyedit
626 EDITOR = StudyEditor()
627 """Singleton study editor instance."""
628
629 ## Return a \b StudyEditor instance to edit the study. 
630 #  \deprecated This function is kept for backward compatibility. Use \a EDITOR instead.
631 #  \ingroup studyedit
632 def getStudyEditor():
633     """
634     Return a :class:`StudyEditor` instance to edit the study.
635     """
636     global EDITOR
637     return EDITOR