Salome HOME
the version delivered by EDF
[modules/hydrosolver.git] / src / HYDROGUI / HYDROSOLVERGUI.py
1 #  Copyright (C) 2012-2013 EDF
2 #
3 #  This file is part of SALOME HYDRO module.
4 #
5 #  SALOME HYDRO module is free software: you can redistribute it and/or modify
6 #  it under the terms of the GNU General Public License as published by
7 #  the Free Software Foundation, either version 3 of the License, or
8 #  (at your option) any later version.
9 #
10 #  SALOME HYDRO module 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
13 #  GNU General Public License for more details.
14 #
15 #  You should have received a copy of the GNU General Public License
16 #  along with SALOME HYDRO module.  If not, see <http://www.gnu.org/licenses/>.
17
18 import sys
19 import os
20 import traceback
21 import logging
22
23 from PyQt4 import QtCore, QtGui
24
25 import salome
26 import SALOME
27 import SalomePyQt
28 sgPyQt = SalomePyQt.SalomePyQt()
29 from salome.kernel.studyedit import getStudyEditor
30 from salome.kernel.logger import Logger
31 from salome.kernel import termcolor
32 logger = Logger("HYDROGUI", color = termcolor.BLUE)
33 #logger.setLevel(logging.ERROR)
34
35 import HYDROSOLVER_ORB
36 from salome.hydro.gui_utils import HSGUIException, wait_cursor, get_and_check_selected_file_path
37 import salome.hydro.study as hydro_study
38 from salome.hydro.mascaret.eficas.appli import EficasForMascaretAppli
39 from salome.hydro.telemac2d.eficas.appli import EficasForTelemac2DAppli
40 from salome.hydro.coupling1d2d.eficas.appli import EficasForCoupling1D2DAppli
41 import salome.hydro.pytel.gui as pytel_gui
42 from salome.hydro.boundary_conditions.eficas.appli import EficasForBoundaryConditionsAppli
43
44 ################################################
45 # GUI context class
46 # Used to store actions, menus, toolbars, etc...
47 ################################################
48
49 class GUIcontext:
50     # menus/toolbars/actions IDs
51     HYDRO_MENU_ID = 90
52     CREATE_MASCARET_CASE_ID = 941
53     RUN_MASCARET_ID = 942
54     EDIT_MASCARET_CASE_ID = 943
55     SHOW_LOG_ID = 944
56     CREATE_TELEMAC2D_CASE_ID = 945
57     RUN_TELEMAC2D_ID = 946
58     EDIT_TELEMAC2D_CASE_ID = 947
59     CREATE_COUPLING1D2D_CASE_ID = 948
60     EDIT_COUPLING1D2D_CASE_ID = 949
61     OPEN_SCHEMA_IN_YACS_ID = 950
62     CREATE_PYTEL_CASE_ID = 951
63     RUN_PYTEL_ID = 952
64     EDIT_PYTEL_CASE_ID = 953
65     GENERATE_JOB = 954
66     DEFINE_BOUNDARY_CONDITIONS_ID = 955
67
68     def __init__( self ):
69         # create top-level menu
70         mid = sgPyQt.createMenu( "Hydro", -1, GUIcontext.HYDRO_MENU_ID, sgPyQt.defaultMenuGroup() )
71         # create toolbar
72         tid = sgPyQt.createTool( "Hydro" )
73         # create actions and fill menu and toolbar with actions
74         a = sgPyQt.createAction( GUIcontext.DEFINE_BOUNDARY_CONDITIONS_ID,
75                                  "Define boundary conditions", "Define boundary conditions",
76                                  "Define the boundary conditions for Telemac",
77                                  "define_boundary_conditions.png" )
78         sgPyQt.createMenu( a, mid )
79         sgPyQt.createTool( a, tid )
80
81         a = sgPyQt.createSeparator()
82         sgPyQt.createMenu( a, mid )
83         sgPyQt.createTool( a, tid )
84
85         a = sgPyQt.createAction( GUIcontext.CREATE_MASCARET_CASE_ID,
86                                  "Create Mascaret case", "Create Mascaret case",
87                                  "Create a new Mascaret case", "create_case1d.png" )
88         sgPyQt.createMenu( a, mid )
89         sgPyQt.createTool( a, tid )
90
91         a = sgPyQt.createAction( GUIcontext.CREATE_TELEMAC2D_CASE_ID,
92                                  "Create Telemac2D case", "Create Telemac2D case",
93                                  "Create a new Telemac2D case", "create_case2d.png" )
94         sgPyQt.createMenu( a, mid )
95         sgPyQt.createTool( a, tid )
96
97         a = sgPyQt.createAction( GUIcontext.CREATE_COUPLING1D2D_CASE_ID,
98                                  "Create 1D / 2D coupling", "Create 1D / 2D coupling",
99                                  "Create a new 1D / 2D coupling", "create_case_couplage.png" )
100         sgPyQt.createMenu( a, mid )
101         sgPyQt.createTool( a, tid )
102
103         a = sgPyQt.createSeparator()
104         sgPyQt.createMenu( a, mid )
105         sgPyQt.createTool( a, tid )
106
107         a = sgPyQt.createAction( GUIcontext.CREATE_PYTEL_CASE_ID,
108                                  "Create case for Pytel execution", "Create case for Pytel execution",
109                                  "Create a new case for Pytel execution", "create_case_pytel.png" )
110         sgPyQt.createMenu( a, mid )
111         sgPyQt.createTool( a, tid )
112
113         # the following action are used in context popup
114         sgPyQt.createAction( GUIcontext.RUN_MASCARET_ID, "Compute case", "Compute case",
115                              "Run Mascaret solver to compute the case" )
116         sgPyQt.createAction( GUIcontext.EDIT_MASCARET_CASE_ID, "Edit case", "Edit case",
117                              "Edit the selected Mascaret case" )
118         sgPyQt.createAction( GUIcontext.SHOW_LOG_ID, "Show log", "Show log",
119                              "Show the log for the selected variable" )
120
121         sgPyQt.createAction( GUIcontext.RUN_TELEMAC2D_ID, "Compute case", "Compute case",
122                              "Run Telemac2D solver to compute the case" )
123         sgPyQt.createAction( GUIcontext.EDIT_TELEMAC2D_CASE_ID, "Edit case", "Edit case",
124                              "Edit the selected Telemac2D case" )
125
126         sgPyQt.createAction( GUIcontext.EDIT_COUPLING1D2D_CASE_ID, "Edit coupling", "Edit coupling",
127                              "Edit the selected 1D / 2D coupling" )
128         sgPyQt.createAction( GUIcontext.OPEN_SCHEMA_IN_YACS_ID, "Open schema in YACS", "Open schema in YACS",
129                              "Open the selected 1D / 2D coupling schema in YACS" )
130
131         sgPyQt.createAction( GUIcontext.RUN_PYTEL_ID, "Compute case", "Compute case",
132                              "Run Pytel launcher to compute the case" )
133         sgPyQt.createAction( GUIcontext.EDIT_PYTEL_CASE_ID, "Edit case", "Edit case",
134                              "Edit the selected Pytel case" )
135         sgPyQt.createAction( GUIcontext.GENERATE_JOB, "Generate batch job", "Generate batch job",
136                              "Generate a batch job to run the selected case")
137
138 ################################################
139 # Global variables
140 ################################################
141
142 # study-to-context map
143 __study2context__   = {}
144 # current context
145 __current_context__ = None
146 # object counter
147 __objectid__ = 0
148
149 ################################################
150 # Internal methods
151 ################################################
152
153 ###
154 # get active study ID
155 ###
156 def _getStudyId():
157     return sgPyQt.getStudyId()
158
159 ###
160 # get active study
161 ###
162 def _getStudy():
163     studyId = _getStudyId()
164     study = getStudyManager().GetStudyByID( studyId )
165     return study
166
167 ###
168 # returns True if object has children
169 ###
170 def _hasChildren( sobj ):
171     if sobj:
172         study = _getStudy()
173         iter  = study.NewChildIterator( sobj )
174         while iter.More():
175             name = iter.Value().GetName()
176             if name:
177                 return True
178             iter.Next()
179             pass
180         pass
181     return False
182
183 ###
184 # get current GUI context
185 ###
186 def _getContext():
187     global __current_context__
188     return __current_context__
189
190 ###
191 # set and return current GUI context
192 # study ID is passed as parameter
193 ###
194 def _setContext( studyID ):
195     global __study2context__, __current_context__
196     if not __study2context__.has_key(studyID):
197         __study2context__[studyID] = GUIcontext()
198         pass
199     __current_context__ = __study2context__[studyID]
200     return __current_context__
201
202 ###
203 # increment object counter in the map
204 ###
205 def _incObjToMap( m, id ):
206     if id not in m: m[id] = 0
207     m[id] += 1
208     pass
209
210 ################################################
211 # Callback functions
212 ################################################
213
214 # called when module is activated
215 # returns True if activating is successfull and False otherwise
216 def activate():
217     ctx = _setContext( _getStudyId() )
218     return True
219
220 # called when module is deactivated
221 def deactivate():
222     pass
223
224 # called when active study is changed
225 # active study ID is passed as parameter
226 def activeStudyChanged( studyID ):
227     ctx = _setContext( _getStudyId() )
228     pass
229
230 # called when popup menu is invoked
231 # popup menu and menu context are passed as parameters
232 def createPopupMenu(popup, context):
233     ed = getStudyEditor()
234     _setContext(ed.studyId)
235     if salome.sg.SelectedCount() == 1:
236         # one object is selected
237         sobj = ed.study.FindObjectID(salome.sg.getSelected(0))
238         selectedType = ed.getTypeId(sobj)
239         if selectedType == hydro_study.MASCARET_CASE_TYPE_ID:
240             popup.addAction(sgPyQt.action(GUIcontext.EDIT_MASCARET_CASE_ID))
241             popup.addAction(sgPyQt.action(GUIcontext.RUN_MASCARET_ID))
242         elif selectedType == hydro_study.TELEMAC2D_CASE_TYPE_ID:
243             popup.addAction(sgPyQt.action(GUIcontext.EDIT_TELEMAC2D_CASE_ID))
244             popup.addAction(sgPyQt.action(GUIcontext.RUN_TELEMAC2D_ID))
245         elif selectedType == hydro_study.COUPLING1D2D_CASE_TYPE_ID:
246             popup.addAction(sgPyQt.action(GUIcontext.EDIT_COUPLING1D2D_CASE_ID))
247             popup.addAction(sgPyQt.action(GUIcontext.OPEN_SCHEMA_IN_YACS_ID))
248         elif selectedType == hydro_study.LOG_TYPE_ID:
249             popup.addAction(sgPyQt.action(GUIcontext.SHOW_LOG_ID))
250         elif selectedType == hydro_study.PYTEL_CASE_TYPE_ID:
251             popup.addAction(sgPyQt.action(GUIcontext.EDIT_PYTEL_CASE_ID))
252             popup.addAction(sgPyQt.action(GUIcontext.RUN_PYTEL_ID))
253             popup.addAction(sgPyQt.action(GUIcontext.GENERATE_JOB))
254
255 # called when GUI action is activated
256 # action ID is passed as parameter
257 def OnGUIEvent( commandID ):
258     try:
259         dict_command[commandID]()
260     except HSGUIException, exc:
261         QtGui.QMessageBox.critical(sgPyQt.getDesktop(),
262                                    QtGui.QApplication.translate("OnGUIEvent", "Error"),
263                                    unicode(exc))
264     except:
265         logger.exception("Unhandled exception caught in HYDROSOLVER GUI")
266         msg = QtGui.QApplication.translate("OnGUIEvent",
267             "Unhandled error happened in HYDROSOLVER module. Please report a bug on "
268             '<a href="https://forge-pleiade.der.edf.fr/projects/salome-rex/issues">SALOME bugtracker</a>, '
269             'category "HYDROSOLVER", describing how you got there and including the following traceback:')
270         msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Critical,
271                                    QtGui.QApplication.translate("OnGUIEvent", "Error"),
272                                    msg,
273                                    parent = sgPyQt.getDesktop())
274         msgBox.setDetailedText(traceback.format_exc())
275         msgBox.exec_()
276
277 ################################################
278 # GUI actions implementation
279 ################################################
280
281 # --------------------------------------------------------------------------------------- #
282 # Dialog box for text display (deprecated, it was only used in the calcium coupling test) #
283 # --------------------------------------------------------------------------------------- #
284 """
285 from TextDisplayDialog import Ui_TextDisplayDialog
286
287 class MyTextDisplayDialog(Ui_TextDisplayDialog, QtGui.QDialog):
288
289     def __init__(self, parent = None, modal = 0):
290         QtGui.QDialog.__init__(self, parent)
291         Ui_TextDisplayDialog.__init__(self)
292         self.setupUi(self)
293         self.connect(self.closeButton, QtCore.SIGNAL("clicked()"), self.close)
294     
295     def set_log(self, log):
296         self.contentTextEdit.setPlainText(log)
297         self.setWindowTitle("Coupling log")
298 """
299
300 ###
301 # Open Eficas for Mascaret to create a new case
302 ###
303 def create_mascaret_case():
304     EficasForMascaretAppli()
305
306 ###
307 # Open Eficas for Mascaret to edit the selected case
308 ###
309 def edit_mascaret_case():
310     EficasForMascaretAppli(get_and_check_selected_file_path())
311
312 # Run Mascaret on selected case
313 def run_mascaret():
314     try:
315         with wait_cursor:
316             ed = hydro_study.HydroStudyEditor()
317             sobj = ed.editor.study.FindObjectID(salome.sg.getSelected(0))
318             (file_list, lig_file, input_vars, output_vars) = ed.get_mascaret_params_from_case(sobj)
319             var_names = [var["NOM"].strip() for var in output_vars]
320             mascaret_vars = [var["VARIABLE_MASCARET"].strip() for var in output_vars]
321             engine = salome.lcc.FindOrLoadComponent("FactoryServer", "MASCARET")
322             logger.debug("Calling MASCARET.Compute(%s, %s, %s)" %
323                          (file_list, lig_file, mascaret_vars))
324             output_values = engine.Compute(file_list, lig_file, mascaret_vars)
325             ed.add_results_to_mascaret_case(sobj, var_names, output_values)
326             salome.sg.updateObjBrowser( 0 )
327     except SALOME.SALOME_Exception, exc:
328         salome.sg.updateObjBrowser( 0 )
329         msg = unicode(QtGui.QApplication.translate("run_mascaret",
330                       "An error happened while trying to run Mascaret:"))
331         msg += "\n" + exc.details.text
332         raise HSGUIException(msg)
333
334 # Display selected log (deprecated, it was only used in the calcium coupling test)
335 def show_log():
336     ed = hydro_study.HydroStudyEditor()
337     sobj = ed.editor.study.FindObjectID(salome.sg.getSelected(0))
338     if sobj is not None:
339         (found, attr) = getStudyEditor().builder.FindAttribute(
340                                                 sobj, "AttributeParameter")
341         log = attr.GetString("log")
342         dialog = MyTextDisplayDialog(sgPyQt.getDesktop())
343         dialog.set_log(log)
344         dialog.show()
345
346 ###
347 # Open Eficas for Telemac2D to create a new case
348 ###
349 def create_telemac2d_case():
350     EficasForTelemac2DAppli()
351
352 ###
353 # Open Eficas for Telemac2D to edit the selected case
354 ###
355 def edit_telemac2d_case():
356     EficasForTelemac2DAppli(get_and_check_selected_file_path())
357
358 # Run Telemac2D on selected case
359 def run_telemac2d():
360     engine = None
361     try:
362         with wait_cursor:
363             ed = hydro_study.HydroStudyEditor()
364             entry = salome.sg.getSelected(0)
365             engine = salome.lcc.FindOrLoadComponent("Telemac2DContainer", "TELEMAC2D")
366             engine.Compute(ed.editor.studyId, entry)
367             # Stop container after execution so that we can use another fortran user file in the next run
368             engine.GetContainerRef().Shutdown()
369     except SALOME.SALOME_Exception, exc:
370         if engine is not None:
371             engine.GetContainerRef().Shutdown()
372         msg = unicode(QtGui.QApplication.translate("run_telemac2d",
373                       "An error happened while trying to run Telemac2D:"))
374         msg += "\n" + exc.details.text
375         raise HSGUIException(msg)
376     except Exception, exc:
377         logger.exception("An error happened in the computation (Telemac2D probably crashed).")
378         try:
379             engine.GetContainerRef().Shutdown()
380         except:
381             pass
382         msg = unicode(QtGui.QApplication.translate("run_telemac2d",
383                       "An error happened in the computation (Telemac2D probably crashed, "
384                       "see logs and listing files for more details):"))
385         msg += "\n" + unicode(exc)
386         raise HSGUIException(msg)
387
388 ###
389 # Open Eficas for 1D / 2D Coupling to create a new coupling case
390 ###
391 def create_coupling1d2d_case():
392     EficasForCoupling1D2DAppli()
393
394 ###
395 # Open Eficas for 1D / 2D Coupling to edit the selected coupling case
396 ###
397 def edit_coupling1d2d_case():
398     EficasForCoupling1D2DAppli(get_and_check_selected_file_path())
399
400 def open_schema_in_yacs():
401     ed = getStudyEditor()
402     sobj = ed.study.FindObjectID(salome.sg.getSelected(0))
403     filename = sobj.GetComment()
404     if filename.endswith(".comm"):
405         filename = filename[:-5] + ".xml"
406     if not os.path.isfile(filename):
407         raise HSGUIException(QtGui.QApplication.translate("open_schema_in_yacs",
408                                   "Schema file %1 does not exist").arg(filename))
409     yg = salome.ImportComponentGUI("YACS")
410     yg.loadSchema(filename)
411
412 ###
413 # Open Eficas for boundary conditions definition
414 ###
415 def define_boundary_conditions():
416     EficasForBoundaryConditionsAppli()
417
418 ###
419 # Commands dictionary
420 ###
421 dict_command = {
422     GUIcontext.CREATE_MASCARET_CASE_ID: create_mascaret_case,
423     GUIcontext.RUN_MASCARET_ID: run_mascaret,
424     GUIcontext.EDIT_MASCARET_CASE_ID: edit_mascaret_case,
425     GUIcontext.SHOW_LOG_ID: show_log,
426     GUIcontext.CREATE_TELEMAC2D_CASE_ID: create_telemac2d_case,
427     GUIcontext.RUN_TELEMAC2D_ID: run_telemac2d,
428     GUIcontext.EDIT_TELEMAC2D_CASE_ID: edit_telemac2d_case,
429     GUIcontext.CREATE_COUPLING1D2D_CASE_ID: create_coupling1d2d_case,
430     GUIcontext.EDIT_COUPLING1D2D_CASE_ID: edit_coupling1d2d_case,
431     GUIcontext.OPEN_SCHEMA_IN_YACS_ID: open_schema_in_yacs,
432     GUIcontext.CREATE_PYTEL_CASE_ID: pytel_gui.create_case,
433     GUIcontext.RUN_PYTEL_ID: pytel_gui.run_selected_case,
434     GUIcontext.EDIT_PYTEL_CASE_ID: pytel_gui.edit_selected_case,
435     GUIcontext.GENERATE_JOB: pytel_gui.generate_job_for_selected_case,
436     GUIcontext.DEFINE_BOUNDARY_CONDITIONS_ID: define_boundary_conditions,
437     }