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