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