Salome HOME
Merge from V6_4_BR 05/12/2011
[modules/smesh.git] / src / Tools / padder / spadderpy / gui / plugindialog.py
1 # -*- coding: iso-8859-1 -*-
2 #  Copyright (C) 2011 EDF R&D
3 #
4 #  This library is free software; you can redistribute it and/or
5 #  modify it under the terms of the GNU Lesser General Public
6 #  License as published by the Free Software Foundation; either
7 #  version 2.1 of the License.
8 #
9 #  This library is distributed in the hope that it will be useful,
10 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 #  Lesser General Public License for more details.
13 #
14 #  You should have received a copy of the GNU Lesser General Public
15 #  License along with this library; if not, write to the Free Software
16 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20 # -* Makefile *- 
21 #
22 # Author : Guillaume Boulant (EDF)
23 #
24
25 from PyQt4.QtGui import QDialog, QIcon
26 from PyQt4.QtCore import QObject, SIGNAL, SLOT, Qt
27
28 from plugindialog_ui import Ui_PluginDialog
29 from inputdialog import InputDialog
30 from inputdata import InputData
31 # __GBO__: uncomment this line and comment the previous one to use the
32 # demo input dialog instead of the real one.
33 #from demoinputdialog import InputDialog
34
35 import os
36 import salome
37 from salome.kernel import studyedit
38 from salome.kernel.uiexception import AdminException
39
40 from omniORB import CORBA
41 import SMESH
42 import smesh
43 import MESHJOB
44
45 gui_states = ["CAN_SELECT", "CAN_COMPUTE", "CAN_REFRESH", "CAN_PUBLISH"]
46
47 run_states = ["CREATED", "IN_PROCESS", "QUEUED", "RUNNING", "PAUSED"];
48 end_states = ["FINISHED", "ERROR"]
49 all_states = run_states+end_states;
50
51 # The SALOME launcher resource is specified by its name as defined in
52 # the file CatalogResources.xml (see root directory of the
53 # application). We could have a check box in the dialog to specify
54 # wether we want a local execution or a remote one.
55 resource_name = "localhost"
56 from salome.smesh.spadder.configreader import ConfigReader
57
58
59 class PluginDialog(QDialog):
60
61     def __init__(self,parent = None,name = None,modal = 0,fl = 0):
62         QDialog.__init__(self,parent)
63         # Set up the user interface from Designer.
64         self.__ui = Ui_PluginDialog()
65         self.__ui.setupUi(self)
66
67         # The default display strategy is to use a separate dialog box
68         # to select the input data.
69         self.viewInputFrame(False)
70
71         # The icon are supposed to be located in the plugin folder,
72         # i.e. in the same folder than this python module file
73         iconfolder=os.path.dirname(os.path.abspath(__file__))
74         icon = QIcon()
75         icon.addFile(os.path.join(iconfolder,"input.png"))
76         self.__ui.btnInput.setIcon(icon)
77         icon = QIcon()
78         icon.addFile(os.path.join(iconfolder,"compute.png"))
79         self.__ui.btnCompute.setIcon(icon)
80         icon = QIcon()
81         icon.addFile(os.path.join(iconfolder,"refresh.png"))
82         self.__ui.btnRefresh.setIcon(icon)
83         icon = QIcon()
84         icon.addFile(os.path.join(iconfolder,"publish.png"))
85         self.__ui.btnPublish.setIcon(icon)
86         icon = QIcon()
87         icon.addFile(os.path.join(iconfolder,"clear.png"))
88         self.__ui.btnClear.setIcon(icon)
89
90         # Then, we can connect the slot to there associated button event
91         self.connect(self.__ui.btnInput,       SIGNAL('clicked()'), self.onInput )
92         self.connect(self.__ui.btnCompute,     SIGNAL('clicked()'), self.onCompute )
93         self.connect(self.__ui.btnRefresh,     SIGNAL('clicked()'), self.onRefresh )
94         self.connect(self.__ui.btnPublish,     SIGNAL('clicked()'), self.onPublish )
95         self.connect(self.__ui.btnClear,       SIGNAL('clicked()'), self.onClear )
96
97         self.clear()
98
99         self.setupJobManager()
100         
101
102     def setupJobManager(self):
103         '''
104         This function configures the jobmanager by transmiting the
105         parameters required for a local execution and a remote
106         execution. The choice between "local" and "remote" is done at
107         the initialize step, by specifing the name of the resource to
108         be used.
109         '''
110         # We first 
111         
112         configReader = ConfigReader()
113         config = configReader.getLocalConfig()
114         configId = config.resname
115         self.__getJobManager().configure(configId, config)
116         # Note that the resname parameter is used as the key identifier of
117         # the configuration in the job manager. As is, there can be then
118         # only one configuration for each machine defined in the resources
119         # catalog (no need to have further, I thing)
120         config = configReader.getRemoteConfig()
121         configId = config.resname
122         self.__getJobManager().configure(configId, config)
123
124         # We specify the default configuration identifier as the
125         # resource name of the default configuration
126         self.__configId = configReader.getDefaultConfig().resname
127
128
129     def viewInputFrame(self, view=True):
130         # By default, the top input frame is visible and the input
131         # button is not visible.
132         if view is False:
133             self.__ui.frameInput.setVisible(False)
134             self.__ui.btnInput.setVisible(True)
135             # We create the input dialog that will be displayed when
136             # button input is pressed:
137             self.__inputDialog = InputDialog(self)
138             # The window is kept on the top to ease the selection of
139             # items in the object browser:
140             self.__inputDialog.setWindowFlags(
141                 self.__inputDialog.windowFlags() | Qt.WindowStaysOnTopHint)
142             # The signal inputValidated emited from inputDialog is
143             # connected to the slot function onProcessInput:
144             self.connect(self.__inputDialog, SIGNAL('inputValidated()'), self.onProcessInput)
145             
146         else:
147             self.__ui.frameInput.setVisible(True)
148             self.__ui.btnInput.setVisible(False)
149             # This case is NOT IMPLEMENTED YET (not really). It could
150             # be used to draw the input frame directly in the frame
151             # frameInput of this dialog box.
152
153     def getInputFrame(self):
154         return self.__ui.frameInput
155         
156     def __setGuiState(self,states=["CAN_SELECT"]):
157         if "CAN_SELECT" in states:
158             self.__ui.btnInput.setEnabled(True)
159         else:
160             self.__ui.btnInput.setEnabled(False)
161             
162         if "CAN_COMPUTE" in states:
163             self.__ui.btnCompute.setEnabled(True)
164         else:
165             self.__ui.btnCompute.setEnabled(False)
166
167         if "CAN_REFRESH" in states:
168             self.__ui.btnRefresh.setEnabled(True)
169         else:
170             self.__ui.btnRefresh.setEnabled(False)
171
172         if "CAN_PUBLISH" in states:
173             self.__ui.btnPublish.setEnabled(True)
174         else:
175             self.__ui.btnPublish.setEnabled(False)
176
177     def __getJobManager(self):
178         """
179         This function requests a pointer to the MeshJobManager
180         servant. Note that the component is loaded on first demand,
181         and then the reference is recycled.
182         """
183         if self.__dict__.has_key("__jobManager") and self.__jobManager is not None:
184             return self.__jobManager
185
186         # WARN: we first have to update the SALOME components catalog
187         # with the SPADDER components (because they are not defined in
188         # the SMESH catalog, and then they are not in the default
189         # catalog)
190         from salome.smesh import spadder
191         spadder.loadSpadderCatalog()
192         # Then we can load the MeshJobManager component
193         component=salome.lcc.FindOrLoadComponent("FactoryServer","MeshJobManager")
194         if component is None:
195             msg="ERR: the SALOME component MeshJobManager can't be reached"
196             self.__log(msg)
197             raise AdminException(msg)
198
199         self.__jobManager = component
200         return self.__jobManager
201
202     def __log(self, message):
203         """
204         This function prints the specified message in the log area
205         """ 
206         self.__ui.txtLog.append(message)
207
208     def __exportMesh(self, meshName, meshObject):
209         '''
210         This function exports the specified mesh object to a med
211         file whose name (basepath) is built from the specified mesh
212         name. This returns the filename.
213         '''
214         filename=str("/tmp/padder_inputfile_"+meshName+".med")
215         meshObject.ExportToMEDX( filename, 0, SMESH.MED_V2_2, 1 )
216         return filename
217
218     def clear(self):
219         """
220         This function clears the log area and the states of the buttons
221         """
222         self.__listInputData = []
223         self.__ui.txtLog.clear()
224         self.__setGuiState(["CAN_SELECT"])
225         self.__isRunning = False
226         self.__ui.lblStatusBar.setText("Ready")
227
228     def update(self):
229         '''
230         This function can be used to programmatically force the
231         refresh of the dialog box, the job state in particular.
232         '''
233         if self.__isRunning:
234             self.onRefresh()
235
236     def onInput(self):
237         '''
238         This function is the slot connected to the Input button
239         (signal clicked()). It opens the dialog window to input
240         data. The dialog is opened in a window modal mode so that the
241         SALOME study objects can be selected. In conterpart, this
242         class must listen to signals emitted by the child dialog
243         windows to process the validation event (see the slot
244         onProcessInput which is connected to this event).
245         '''
246         self.__inputDialog.setData(self.__listInputData)
247         self.__inputDialog.open()
248
249     def onProcessInput(self):
250         """
251         This function is the slot connected to the signal
252         inputValidated(), emit by the input dialog window when it's
253         validated, i.e. OK is pressed and data are valid.
254         """
255         # The processing simply consists in requesting the input data
256         # from the dialog window.
257         self.__listInputData = self.__inputDialog.getData()
258         self.__ui.lblStatusBar.setText("Input data OK")
259         self.__log("INF: Press \"Compute\" to start the job")
260         self.__setGuiState(["CAN_SELECT", "CAN_COMPUTE"])
261         
262     def onCompute(self):
263         '''
264         This function is the slot connected to the Compute button. It
265         initializes a mesh computation job and start it using the
266         SALOME launcher.  
267         '''
268         # We first have to create the list of parameters for the
269         # initialize function. For that, we have to create the files
270         # from the mesh objects:
271         meshJobParameterList=[]
272         concreteIndex=0
273         for inputData in self.__listInputData:
274             # For each input data, we have to create a
275             # MeshJobParameter and add it to the list.
276             filename  = self.__exportMesh(inputData.meshName, inputData.meshObject)
277             if inputData.meshType == InputData.MESHTYPES.CONCRETE:
278                 filetype = MESHJOB.MED_CONCRETE
279             else:
280                 filetype = MESHJOB.MED_STEELBAR
281
282             parameter = MESHJOB.MeshJobParameter(
283                 file_name  = filename,
284                 file_type  = filetype,
285                 group_name = inputData.groupName)
286             meshJobParameterList.append(parameter)
287
288         jobManager = self.__getJobManager()
289         self.__jobid = jobManager.initialize(meshJobParameterList, self.__configId)
290         if self.__jobid < 0:
291             self.__log("ERR: the job can't be initialized")
292             return
293         self.__log("INF: the job has been initialized with jobid = "+str(self.__jobid))
294         
295         startOk = jobManager.start(self.__jobid)
296         if not startOk:
297             self.__log("ERR: the job with jobid = "+str(self.__jobid)+" can't be started")
298             return
299         self.__log("INF: the job "+str(self.__jobid)+" has been started")
300         self.__ui.lblStatusBar.setText("Submission OK")
301         self.__setGuiState(["CAN_REFRESH"])
302         self.__isRunning = True
303
304     def onRefresh(self):
305         """
306         This function is the slot connected on the Refresh button. It
307         requests the mesh job manager to get the state of the job and
308         display it in the log area.
309         """
310         jobManager = self.__getJobManager()
311         state = jobManager.getState(self.__jobid)
312         self.__log("INF: job state = "+str(state))
313         self.__ui.lblStatusBar.setText("")
314         if state in run_states:
315             return
316
317         self.__isRunning = False
318         if state == "FINISHED":
319             self.__setGuiState(["CAN_PUBLISH"])
320         else:
321             self.__setGuiState(["CAN_SELECT"])
322
323
324     def onPublish(self):
325         """
326         This function is the slot connected on the Publish button. It
327         requests the mesh job manager to download the results data
328         from the computation resource host and load the med file in
329         the SALOME study. 
330         """
331         jobManager = self.__getJobManager()
332         state = jobManager.getState(self.__jobid)
333         if state in run_states:
334             self.__log("WRN: jobid = "+str(self.__jobid)+" is not finished (state="+str(state)+")")
335             return
336
337         if state not in end_states:
338             self.__log("ERR: jobid = "+str(self.__jobid)+" ended abnormally with state="+str(state))
339             return
340
341         meshJobResults = jobManager.finalize(self.__jobid)
342         if state == "ERROR":
343             self.__log("ERR: jobid = "+str(self.__jobid)+" ended with error: "+meshJobResults.status)
344             return
345
346         logsdirname = os.path.join(meshJobResults.results_dirname, "logs")
347         self.__log("INF:  jobid="+str(self.__jobid)+" ended normally   : "+meshJobResults.status)
348         self.__log("INF:  jobid="+str(self.__jobid)+" see log files in : "+logsdirname)
349
350         medfilename = os.path.join(meshJobResults.results_dirname,
351                                    meshJobResults.outputmesh_filename)
352
353         smesh.SetCurrentStudy(studyedit.getActiveStudy())
354         ([outputMesh], status) = smesh.CreateMeshesFromMED(medfilename)
355
356         # By convention, the name of the output mesh in the study is
357         # build from a constant string 'padder' and the session jobid
358         meshname = 'padder_'+str(self.__jobid)
359         smesh.SetName(outputMesh.GetMesh(), meshname)
360         if salome.sg.hasDesktop():
361             salome.sg.updateObjBrowser(0)
362
363         self.__ui.lblStatusBar.setText("Publication OK")
364         self.__setGuiState(["CAN_SELECT"])
365
366     def onClear(self):
367         """
368         This function is the slot connected on the Clear button. It
369         erases data in the dialog box and cancel the current job if
370         one is running.
371         """
372         self.clear()
373         
374
375
376 __dialog=None
377 def getDialog():
378     """
379     This function returns a singleton instance of the plugin dialog. 
380     """
381     global __dialog
382     if __dialog is None:
383         __dialog = PluginDialog()
384     return __dialog
385
386 #
387 # ==============================================================================
388 # Basic use cases and unit test functions
389 # ==============================================================================
390 #
391 def TEST_PluginDialog():
392     import sys
393     from PyQt4.QtGui import QApplication
394     from PyQt4.QtCore import QObject, SIGNAL, SLOT
395     app = QApplication(sys.argv)
396     QObject.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
397
398     dlg=PluginDialog()
399     dlg.exec_()
400
401 if __name__ == "__main__":
402     TEST_PluginDialog()
403
404         
405