Salome HOME
Updated copyright comment
[samples/genericsolver.git] / src / GENERICSOLVERGUI / GENERICSOLVERGUI.py
1 # Copyright (C) 2009-2024  EDF
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 # $Id$
21 #
22
23 import logging
24 import traceback
25
26 from PyQt5 import QtCore, QtGui, QtWidgets
27 from salome.kernel import termcolor
28 from salome.kernel.logger import Logger
29 from salome.kernel.parametric import study_exchange_vars
30 from salome.kernel.studyedit import getStudyEditor
31 import SALOME
32 import SALOME_TYPES
33 import salome
34
35 import SalomePyQt
36
37
38 # Get SALOME PyQt interface
39 sgPyQt = SalomePyQt.SalomePyQt()
40
41 logger = Logger("GENERICSOLVERGUI", color=termcolor.RED_FG)
42
43 salome.salome_init()
44
45 VARS_ICON = "icon_variables.png"
46 SOLVER_ENGINE_NAME = "DEVIATION"
47
48 ################################################
49 # GUI context class
50 # Used to store actions, menus, toolbars, etc...
51 ################################################
52
53
54 class GUIcontext:
55     # module name
56     MODULE_NAME = "GENERICSOLVER"
57     # module icon
58     MODULE_PIXMAP = "GENERICSOLVER_small.png"
59     # data objects IDs
60     MODULE_ID = 1000
61     CASE_ID = 1020
62     VARIABLE_ID = 1030
63     # menus/toolbars/actions IDs
64     GENERICSOLVER_MENU_ID = 90
65     SOLVER_ID = 941
66     OPTIONS_ID = 943
67     OPTION_1_ID = 944
68     OPTION_2_ID = 945
69     OPTION_3_ID = 946
70     DELETE_ALL_ID = 951
71     CREATE_CASE_ID = 955
72     SET_VALUE_ID = 956
73     # default object name
74     DEFAULT_CASE_NAME = "Case"
75
76     # constructor
77     def __init__(self):
78         # create top-level menu
79         mid = sgPyQt.createMenu("Genericsolver", -1, GUIcontext.GENERICSOLVER_MENU_ID,
80                                 sgPyQt.defaultMenuGroup())
81         # create toolbar
82         tid = sgPyQt.createTool("Genericsolver")
83         # create actions and fill menu and toolbar with actions
84         a = sgPyQt.createAction(GUIcontext.CREATE_CASE_ID, "Create case", "Create case",
85                                 "Create a new case", "CaseGENERICSOLVER.png")
86         sgPyQt.createMenu(a, mid)
87         sgPyQt.createTool(a, tid)
88         a = sgPyQt.createSeparator()
89         sgPyQt.createMenu(a, mid)
90         ag = sgPyQt.createActionGroup(GUIcontext.OPTIONS_ID)
91         ag.setText("Creation mode")
92         ag.setUsesDropDown(True)
93         a = sgPyQt.createAction(GUIcontext.OPTION_1_ID, "Default name", "Default name",
94                                 "Use default name for the objects")
95         a.setCheckable(True)
96         ag.add(a)
97         a = sgPyQt.createAction(GUIcontext.OPTION_2_ID, "Generate name", "Generate name",
98                                 "Generate name for the objects")
99         a.setCheckable(True)
100         ag.add(a)
101         a = sgPyQt.createAction(GUIcontext.OPTION_3_ID, "Ask name", "Ask name",
102                                 "Request object name from the user")  # , "", 0, True)
103         a.setCheckable(True)
104         ag.add(a)
105         sgPyQt.createMenu(ag, mid)
106         sgPyQt.createTool(ag, tid)
107         default_mode = sgPyQt.integerSetting("GENERICSOLVER", "creation_mode", 0)
108         sgPyQt.action(GUIcontext.OPTION_1_ID + default_mode).setChecked(True)
109         # the following action are used in context popup
110         a = sgPyQt.createAction(GUIcontext.DELETE_ALL_ID, "Delete all", "Delete all",
111                                 "Delete all objects")
112         a = sgPyQt.createAction(GUIcontext.SET_VALUE_ID, "Set value", "Set Value",
113                                 "Set a new value to variable")
114         a = sgPyQt.createAction(GUIcontext.SOLVER_ID, "Run Solver", "Run Solver",
115                                 "Run Solver on selected case", "ExecGENERICSOLVER.png")
116
117 ################################################
118 # Global variables
119 ################################################
120
121 # study-to-context map
122 __study2context__ = {}
123 # current context
124 __current_context__ = None
125 # object counter
126 __id__ = 0
127
128 ################################################
129 # Internal methods
130 ################################################
131
132
133 # ##
134 # get active study ID
135 # ##
136 def getStudyId():
137     return sgPyQt.getStudyId()
138
139
140 # ##
141 # get active study
142 # ##
143 def getStudy():
144     studyId = getStudyId()
145     study = salome.myStudyManager.GetStudyByID(studyId)
146     return study
147
148
149 # ##
150 # returns True if object has children
151 # ##
152 def hasChildren(sobj):
153     if sobj:
154         study = getStudy()
155         iterator = study.NewChildIterator(sobj)
156         while iterator.More():
157             name = iterator.Value().GetName()
158             if name:
159                 return True
160             iterator.Next()
161             pass
162         pass
163     return False
164
165
166 # ##
167 # get current GUI context
168 # ##
169 def getContext():
170     global __current_context__
171     return __current_context__
172
173
174 # ##
175 # set and return current GUI context
176 # study ID is passed as parameter
177 # ##
178 def setContext(studyID):
179     global __study2context__, __current_context__
180     if studyID not in __study2context__:
181         __study2context__[studyID] = GUIcontext()
182         pass
183     __current_context__ = __study2context__[studyID]
184     return __current_context__
185
186 ################################################
187 # Callback functions
188 ################################################
189
190
191 # called when module is initialized
192 # perform initialization actions
193 def initialize():
194     logger.debug("GENERICSOLVERGUI.initialize() : study : %d" % getStudyId())
195     # set default preferences values
196     if not sgPyQt.hasSetting("GENERICSOLVER", "def_case_name"):
197         sgPyQt.addSetting("GENERICSOLVER", "def_case_name", GUIcontext.DEFAULT_CASE_NAME)
198     if not sgPyQt.hasSetting("GENERICSOLVER", "creation_mode"):
199         sgPyQt.addSetting("GENERICSOLVER", "creation_mode", 0)
200     pass
201
202
203 # called when module is initialized
204 # return map of popup windows to be used by the module
205 def windows():
206     logger.debug("GENERICSOLVERGUI.windows() : study : %d" % getStudyId())
207     wm = {}
208     wm[SalomePyQt.WT_ObjectBrowser] = QtCore.Qt.LeftDockWidgetArea
209     wm[SalomePyQt.WT_PyConsole] = QtCore.Qt.BottomDockWidgetArea
210     return wm
211
212
213 # called when module is initialized
214 # return list of 2d/3d views to be used ny the module
215 def views():
216     logger.debug("GENERICSOLVERGUI.views() : study : %d" % getStudyId())
217     return []
218
219
220 # called when module is initialized
221 # export module's preferences
222 def createPreferences():
223     logger.debug("GENERICSOLVERGUI.createPreferences() : study : %d" % getStudyId())
224     gid = sgPyQt.addPreference("General")
225     gid = sgPyQt.addPreference("Object creation", gid)
226     sgPyQt.addPreference("Default case name", gid, SalomePyQt.PT_String,
227                          "GENERICSOLVER", "def_case_name")
228     pid = sgPyQt.addPreference("Default creation mode", gid, SalomePyQt.PT_Selector,
229                                "GENERICSOLVER", "creation_mode")
230     strings = ["Default name", "Generate name", "Ask name"]
231     indexes = [0, 1, 2]
232     sgPyQt.setPreferenceProperty(pid, "strings", strings)
233     sgPyQt.setPreferenceProperty(pid, "indexes", indexes)
234     pass
235
236
237 # called when module is activated
238 # returns True if activating is successfull and False otherwise
239 def activate():
240     logger.debug("GENERICSOLVERGUI.activate() : study : %d" % getStudyId())
241     setContext(getStudyId())
242     return True
243
244
245 # called when module is deactivated
246 def deactivate():
247     logger.debug("GENERICSOLVERGUI.deactivate() : study : %d" % getStudyId())
248     pass
249
250
251 # called when active study is changed
252 # active study ID is passed as parameter
253 def activeStudyChanged(studyID):
254     logger.debug("GENERICSOLVERGUI.activeStudyChanged(): study : %d" % studyID)
255     setContext(getStudyId())
256     pass
257
258
259 # called when popup menu is invoked
260 # popup menu and menu context are passed as parameters
261 def createPopupMenu(popup, context):
262     logger.debug("GENERICSOLVERGUI.createPopupMenu(): context = %s" % context)
263     ed = getStudyEditor()
264     setContext(ed.studyId)
265     if salome.sg.SelectedCount() == 1:  # one object is selected
266         typeId = ed.getTypeId(ed.study.FindObjectID(salome.sg.getSelected(0)))
267         if typeId == GUIcontext.MODULE_ID:
268             # menu for component
269             popup.addAction(sgPyQt.action(GUIcontext.DELETE_ALL_ID))
270         elif typeId == GUIcontext.CASE_ID:
271             # menu for case
272             popup.addAction(sgPyQt.action(GUIcontext.SOLVER_ID))
273         elif typeId == GUIcontext.VARIABLE_ID:
274             # menu for case
275             popup.addAction(sgPyQt.action(GUIcontext.SET_VALUE_ID))
276
277
278 # called when GUI action is activated
279 # action ID is passed as parameter
280 def OnGUIEvent(commandID):
281     logger.debug("GENERICSOLVERGUI.OnGUIEvent(): command = %d" % commandID)
282     if commandID in dict_command:
283         try:
284             dict_command[commandID]()
285         except:
286             traceback.print_exc()
287     elif commandID in (GUIcontext.OPTION_1_ID, GUIcontext.OPTION_2_ID, GUIcontext.OPTION_3_ID):
288         pass  # Option selection does not trigger a method
289     else:
290         logger.error("The command is not implemented: %d" % commandID)
291     pass
292
293
294 # called when module's preferences are changed
295 # preference's resources section and setting name are passed as parameters
296 def preferenceChanged(section, setting):
297     logger.debug("GENERICSOLVERGUI.preferenceChanged(): %s / %s" % (section, setting))
298     pass
299
300
301 # called when active view is changed
302 # view ID is passed as parameter
303 def activeViewChanged(viewID):
304     logger.debug("GENERICSOLVERGUI.activeViewChanged(): %d" % viewID)
305     pass
306
307
308 # called when active view is cloned
309 # cloned view ID is passed as parameter
310 def viewCloned(viewID):
311     logger.debug("GENERICSOLVERGUI.viewCloned(): %d" % viewID)
312     pass
313
314
315 # called when active view is viewClosed
316 # view ID is passed as parameter
317 def viewClosed(viewID):
318     logger.debug("GENERICSOLVERGUI.viewClosed(): %d" % viewID)
319     pass
320
321 ################################################
322 # GUI actions implementation
323 ################################################
324
325
326 # ##
327 # Create a deterministic case
328 # ##
329 def CreateCase():
330     logger.debug("GENERICSOLVERGUI.CreateCase : enter")
331     default_case_name = sgPyQt.stringSetting("GENERICSOLVER", "def_case_name",
332                                              GUIcontext.DEFAULT_CASE_NAME).strip()
333     global __id__
334     try:
335         if sgPyQt.action(GUIcontext.OPTION_3_ID).isChecked():
336             # request object name from the user
337             name, ok = QtWidgets.QInputDialog.getText(sgPyQt.getDesktop(),
338                                                       "Create case",
339                                                       "Enter case name:",
340                                                       QtWidgets.QLineEdit.Normal,
341                                                       default_case_name)
342             if not ok:
343                 return
344             name = name.strip()
345         elif sgPyQt.action(GUIcontext.OPTION_2_ID).isChecked():
346             # generate object name
347             __id__ = __id__ + 1
348             name = "%s %d" % (default_case_name, __id__)
349         else:
350             name = default_case_name
351             pass
352         pass
353     except Exception as e:
354         logger.debug(e)
355         # generate object name
356         __id__ = __id__ + 1
357         name = "%s %d" % (default_case_name, __id__)
358         pass
359     if not name:
360         return
361     ed = getStudyEditor()
362     father = ed.findOrCreateComponent(GUIcontext.MODULE_NAME,
363                                       icon=GUIcontext.MODULE_PIXMAP)
364     ed.setTypeId(father, GUIcontext.MODULE_ID)
365     case = ed.findItem(father, name)
366     if case is None:
367         case = ed.createItem(father, name, typeId=GUIcontext.CASE_ID)
368         varE = ed.createItem(case, "E", typeId=GUIcontext.VARIABLE_ID)
369         ed.setAttributeValue(varE, "AttributeReal", 210.e9)
370         varF = ed.createItem(case, "F", typeId=GUIcontext.VARIABLE_ID)
371         ed.setAttributeValue(varF, "AttributeReal", 1000.)
372         varL = ed.createItem(case, "L", typeId=GUIcontext.VARIABLE_ID)
373         ed.setAttributeValue(varL, "AttributeReal", 1.5)
374         varI = ed.createItem(case, "I", typeId=GUIcontext.VARIABLE_ID)
375         ed.setAttributeValue(varI, "AttributeReal", 2.e-6)
376
377         inputVarList = [study_exchange_vars.Variable("E"),
378                         study_exchange_vars.Variable("F"),
379                         study_exchange_vars.Variable("L"),
380                         study_exchange_vars.Variable("I")]
381         outputVarList = [study_exchange_vars.Variable("dev")]
382         exchVars = study_exchange_vars.ExchangeVariables(inputVarList, outputVarList)
383         study_exchange_vars.createSObjectForExchangeVariables(case, exchVars, icon=VARS_ICON)
384
385     salome.sg.updateObjBrowser(True)
386     logger.debug("GENERICSOLVERGUI.CreateCase : exit")
387
388
389 # ##
390 # Run the SOLVER
391 # ##
392 def RunSOLVER():
393     ed = getStudyEditor()
394     # Get selected case
395     entry = salome.sg.getSelected(0)
396     assert entry is not None
397     sobj = ed.study.FindObjectID(entry)
398     assert sobj
399     assert ed.getTypeId(sobj) == GUIcontext.CASE_ID
400     assert hasChildren(sobj)
401
402     # Initialize and run the solver
403     solver_engine = salome.lcc.FindOrLoadComponent("FactoryServer", SOLVER_ENGINE_NAME)
404     assert solver_engine
405     solver_engine.Init(getStudyId(), entry)
406
407     param_input = SALOME_TYPES.ParametricInput(inputVarList=[],
408                                                outputVarList=["dev"],
409                                                inputValues=[[[]]],
410                                                specificParameters=[])
411     error = None
412
413     try:
414         param_output = solver_engine.Exec(param_input)
415     except SALOME.SALOME_Exception as exc:
416         error = exc.details.text
417
418     solver_engine.Finalize()
419
420     if error is not None:
421         QtWidgets.critical(sgPyQt.getDesktop(), "Error", error)
422     elif param_output.returnCode != 0:
423         QtWidgets.critical(sgPyQt.getDesktop(), "Error", param_output.errorMessage)
424     else:
425         # Add result to deterministic case in object browser
426         var = ed.findOrCreateItem(sobj, "Deviation")
427         ed.setAttributeValue(var, "AttributeReal", param_output.outputValues[0][0][0][0])
428         salome.sg.updateObjBrowser(False)
429
430
431 # ##
432 # Delete all objects
433 # ##
434 def DeleteAll():
435     study = getStudy()
436     father = study.FindComponent(GUIcontext.MODULE_NAME)
437     if father:
438         iterator = study.NewChildIterator(father)
439         builder = study.NewBuilder()
440         while iterator.More():
441             sobj = iterator.Value()
442             iterator.Next()
443             builder.RemoveObjectWithChildren(sobj)
444             pass
445         salome.sg.updateObjBrowser(True)
446         pass
447     pass
448
449
450 # ##
451 # Set value to variable
452 # ##
453 def SetValue():
454     ed = getStudyEditor()
455     entry = salome.sg.getSelected(0)
456     assert entry is not None
457     sobj = ed.study.FindObjectID(entry)
458     assert sobj
459     oldvalue = ed.getAttributeValue(sobj, "AttributeReal", 0.0)
460     name, ok = QtWidgets.QInputDialog.getText(sgPyQt.getDesktop(),
461                                               "Set a value",
462                                               "Enter new value:",
463                                               QtWidgets.QLineEdit.Normal,
464                                               '%f' % oldvalue)
465     try:
466         value = float(name.strip())
467     except ValueError:
468         value = None
469     if not ok or value is None:
470         return
471     ed.setAttributeValue(sobj, "AttributeReal", value)
472     salome.sg.updateObjBrowser(False)
473
474 # ##
475 # Commands dictionary
476 # ##
477 dict_command = {
478     GUIcontext.SOLVER_ID: RunSOLVER,
479     GUIcontext.CREATE_CASE_ID: CreateCase,
480     GUIcontext.DELETE_ALL_ID: DeleteAll,
481     GUIcontext.SET_VALUE_ID: SetValue,
482     }