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