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