Salome HOME
IMP: SMESH/Yams plug-in: Update to use Meshgems-SurfOpt 1.1 (new name of Yams)
[modules/smesh.git] / src / Tools / YamsPlug / monYamsPlugDialog.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2007-2013  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
21 # Modules Python
22 # Modules Eficas
23
24 import os, subprocess
25 from YamsPlugDialog import Ui_YamsPlugDialog
26 from monViewText import MonViewText
27 from PyQt4.QtGui import *
28 from PyQt4.QtCore import *
29
30
31 class MonYamsPlugDialog(Ui_YamsPlugDialog,QWidget):
32   """
33   """
34   def __init__(self):
35     QWidget.__init__(self)
36     self.setupUi(self)
37     self.connecterSignaux()
38     self.fichierIn=""
39     self.fichierOut=""
40     self.MeshIn=""
41     self.commande=""
42     self.num=1
43     self.__selectedMesh=None
44
45     # complex whith QResources: not used
46     # The icon are supposed to be located in the $SMESH_ROOT_DIR/share/salome/resources/smesh folder,
47     # other solution could be in the same folder than this python module file:
48     # iconfolder=os.path.dirname(os.path.abspath(__file__))
49
50     self.iconfolder=os.environ["SMESH_ROOT_DIR"]+"/share/salome/resources/smesh"
51     #print "monYamsPlugDialog iconfolder",iconfolder
52     icon = QIcon()
53     icon.addFile(os.path.join(self.iconfolder,"select1.png"))
54     self.PB_LoadHyp.setIcon(icon)
55     self.PB_LoadHyp.setToolTip("hypothesis from Salome Object Browser")
56     self.PB_SaveHyp.setIcon(icon)
57     self.PB_SaveHyp.setToolTip("hypothesis to Salome Object Browser")
58     self.PB_MeshSmesh.setIcon(icon)
59     self.PB_MeshSmesh.setToolTip("source mesh from Salome Object Browser")
60     icon = QIcon()
61     icon.addFile(os.path.join(self.iconfolder,"open.png"))
62     self.PB_ParamsFileExplorer.setIcon(icon)
63     self.PB_Load.setIcon(icon)
64     self.PB_Load.setToolTip("hypothesis from file")
65     self.PB_Save.setIcon(icon)
66     self.PB_Save.setToolTip("hypothesis to file")
67     self.PB_MeshFile.setIcon(icon)
68     self.PB_MeshFile.setToolTip("source mesh from a file in disk")
69
70     #Ces parametres ne sont pas remis à rien par le clean
71     self.paramsFile= os.path.abspath(os.path.join(os.environ["HOME"],".yams.dat"))
72     self.LE_ParamsFile.setText(self.paramsFile)
73     self.LE_MeshFile.setText("")
74     self.LE_MeshSmesh.setText("")
75     
76     v1=QDoubleValidator(self)
77     v1.setBottom(0.)
78     #v1.setTop(1000.) #per thousand... only if relative
79     v1.setDecimals(2)
80     self.SP_Tolerance.setValidator(v1)
81     self.SP_Tolerance.titleForWarning="Chordal Tolerance"
82     
83     self.resize(800, 600)
84     self.clean()
85
86   def connecterSignaux(self) :
87     self.connect(self.PB_Cancel,SIGNAL("clicked()"),self.PBCancelPressed)
88     self.connect(self.PB_Default,SIGNAL("clicked()"),self.clean)
89     self.connect(self.PB_Help,SIGNAL("clicked()"),self.PBHelpPressed)
90     self.connect(self.PB_OK,SIGNAL("clicked()"),self.PBOKPressed)
91     
92     self.connect(self.PB_Load,SIGNAL("clicked()"),self.PBLoadPressed)
93     self.connect(self.PB_Save,SIGNAL("clicked()"),self.PBSavePressed)
94     self.connect(self.PB_LoadHyp,SIGNAL("clicked()"),self.PBLoadHypPressed)
95     self.connect(self.PB_SaveHyp,SIGNAL("clicked()"),self.PBSaveHypPressed)
96     
97     self.connect(self.PB_MeshFile,SIGNAL("clicked()"),self.PBMeshFilePressed)
98     self.connect(self.PB_MeshSmesh,SIGNAL("clicked()"),self.PBMeshSmeshPressed)
99     self.connect(self.LE_MeshSmesh,SIGNAL("returnPressed()"),self.meshSmeshNameChanged)
100     self.connect(self.PB_ParamsFileExplorer,SIGNAL("clicked()"),self.setParamsFileName)
101     self.connect(self.LE_MeshFile,SIGNAL("returnPressed()"),self.meshFileNameChanged)
102     self.connect(self.LE_ParamsFile,SIGNAL("returnPressed()"),self.paramsFileNameChanged)
103
104   def PBHelpPressed(self):
105     try :
106       mydir=os.environ["SMESH_ROOT_DIR"]
107     except Exception:
108       QMessageBox.warning(self, "Help", "Help unavailable $SMESH_ROOT_DIR not found")
109       return
110     maDoc=mydir+"/share/doc/salome/gui/SMESH/yams/_downloads/mg-surfopt_user_manual.pdf"
111     command="xdg-open "+maDoc+";"
112     subprocess.call(command, shell=True)
113
114   def PBOKPressed(self):
115     if not(self.PrepareLigneCommande()):
116       #warning done yet
117       #QMessageBox.warning(self, "Compute", "Command not found")
118       return
119     maFenetre=MonViewText(self,self.commande)
120
121   def enregistreResultat(self):
122     import salome
123     import SMESH
124     from salome.kernel import studyedit
125     from salome.smesh import smeshBuilder
126     smesh = smeshBuilder.New(salome.myStudy)
127     
128     if not os.path.isfile(self.fichierOut):
129       QMessageBox.warning(self, "Compute", "Result file "+self.fichierOut+" not found")
130
131     maStudy=studyedit.getActiveStudy()
132     smesh.SetCurrentStudy(maStudy)
133     (outputMesh, status) = smesh.CreateMeshesFromGMF(self.fichierOut)
134     name=str(self.LE_MeshSmesh.text())
135     initialMeshFile=None
136     initialMeshObject=None
137     if name=="":
138       a=str(self.fichierIn)
139       name=os.path.basename(os.path.splitext(a)[0])
140       initialMeshFile=a
141     else:
142       initialMeshObject=maStudy.FindObjectByName(name ,"SMESH")[0]
143
144     meshname = name+"_YAMS_"+str(self.num)
145     smesh.SetName(outputMesh.GetMesh(), meshname)
146     outputMesh.Compute() #no algorithms message for "Mesh_x" has been computed with warnings: -  global 1D algorithm is missing
147
148     self.editor = studyedit.getStudyEditor()
149     moduleEntry=self.editor.findOrCreateComponent("SMESH","SMESH")
150     HypReMeshEntry = self.editor.findOrCreateItem(
151         moduleEntry, name = "Plugins Hypotheses", icon="mesh_tree_hypo.png") #, comment = "HypoForRemeshing" )
152     
153     monStudyBuilder=maStudy.NewBuilder()
154     monStudyBuilder.NewCommand()
155     newStudyIter=monStudyBuilder.NewObject(HypReMeshEntry)
156     self.editor.setAttributeValue(newStudyIter, "AttributeName", "YAMS Parameters_"+str(self.num))
157     self.editor.setAttributeValue(newStudyIter, "AttributeComment", self.getResumeData(separator=" ; "))
158     
159     SOMesh=maStudy.FindObjectByName(meshname ,"SMESH")[0]
160     
161     if initialMeshFile!=None:
162       newStudyFileName=monStudyBuilder.NewObject(SOMesh)
163       self.editor.setAttributeValue(newStudyFileName, "AttributeName", "meshFile")
164       self.editor.setAttributeValue(newStudyFileName, "AttributeExternalFileDef", initialMeshFile)
165       self.editor.setAttributeValue(newStudyFileName, "AttributeComment", initialMeshFile)
166
167     if initialMeshObject!=None:
168       newLink=monStudyBuilder.NewObject(SOMesh)
169       monStudyBuilder.Addreference(newLink, initialMeshObject)
170
171     newLink=monStudyBuilder.NewObject(SOMesh)
172     monStudyBuilder.Addreference(newLink, newStudyIter)
173
174     if salome.sg.hasDesktop(): salome.sg.updateObjBrowser(0)
175     self.num+=1
176     return True
177
178   def PBSavePressed(self):
179     from datetime import datetime
180     if not(self.PrepareLigneCommande()): return
181     text = "# YAMS hypothesis parameters\n" 
182     text += "# Params for mesh : " +  self.LE_MeshSmesh.text() +"\n"
183     text += datetime.now().strftime("# Date : %d/%m/%y %H:%M:%S\n")
184     text += "# Command : "+self.commande+"\n"
185     text += self.getResumeData(separator="\n")
186     text += "\n\n"
187
188     try:
189       f=open(self.paramsFile,"a")
190     except:
191       QMessageBox.warning(self, "File", "Unable to open "+self.paramsFile)
192       return
193     try:
194       f.write(text)
195     except:
196       QMessageBox.warning(self, "File", "Unable to write "+self.paramsFile)
197       return
198     f.close()
199
200   def PBSaveHypPressed(self):
201     """save hypothesis in Object Browser"""
202     import salome
203     import SMESH
204     from salome.kernel import studyedit
205     from salome.smesh import smeshBuilder
206     smesh = smeshBuilder.New(salome.myStudy)
207
208     maStudy=studyedit.getActiveStudy()
209     smesh.SetCurrentStudy(maStudy)
210     
211     self.editor = studyedit.getStudyEditor()
212     moduleEntry=self.editor.findOrCreateComponent("SMESH","SMESH")
213     HypReMeshEntry = self.editor.findOrCreateItem(
214         moduleEntry, name = "Plugins Hypotheses", icon="mesh_tree_hypo.png") #, comment = "HypoForRemeshing" )
215     
216     monStudyBuilder=maStudy.NewBuilder()
217     monStudyBuilder.NewCommand()
218     newStudyIter=monStudyBuilder.NewObject(HypReMeshEntry)
219     self.editor.setAttributeValue(newStudyIter, "AttributeName", "YAMS Parameters_"+str(self.num))
220     self.editor.setAttributeValue(newStudyIter, "AttributeComment", self.getResumeData(separator=" ; "))
221     
222     if salome.sg.hasDesktop(): salome.sg.updateObjBrowser(0)
223     self.num+=1
224     return True
225
226   def SP_toStr(self, widget):
227     """only for a QLineEdit widget"""
228     #cr, pos=widget.validator().validate(res, 0) #n.b. "1,3" is acceptable !locale!
229     try:
230       val=float(widget.text())
231     except:
232       QMessageBox.warning(self, widget.titleForWarning, "float value is incorrect: '"+widget.text()+"'")
233       res=str(widget.validator().bottom())
234       widget.setProperty("text", res)
235       return res
236     valtest=widget.validator().bottom()
237     if valtest!=None:
238       if val<valtest:
239         QMessageBox.warning(self, widget.titleForWarning, "float value is under minimum: "+str(val)+" < "+str(valtest))
240         res=str(valtest)
241         widget.setProperty("text", res)
242         return res
243     valtest=widget.validator().top()
244     if valtest!=None:
245       if val>valtest:
246         QMessageBox.warning(self, widget.titleForWarning, "float value is over maximum: "+str(val)+" > "+str(valtest))
247         res=str(valtest)
248         widget.setProperty("text", res)
249         return res    
250     return str(val)
251
252   def getResumeData(self, separator="\n"):
253     text=""
254     for RB in self.GBOptim.findChildren(QRadioButton,):
255       if RB.isChecked()==True:
256         text+="Optimisation="+RB.text()+separator
257         break
258     if self.RB_Absolute.isChecked():
259       text+="Units=absolute"+separator
260     else:
261       text+="Units=relative"+separator
262     v=self.SP_toStr(self.SP_Tolerance)
263     text+="ChordalToleranceDeviation="+v+separator
264     text+="RidgeDetection="+str(self.CB_Ridge.isChecked())+separator
265     text+="SplitEdge="+str(self.CB_SplitEdge.isChecked())+separator
266     text+="PointSmoothing="+str(self.CB_Point.isChecked())+separator
267     text+="GeometricalApproximation="+str(self.SP_Geomapp.value())+separator
268     text+="RidgeAngle="+str(self.SP_Ridge.value())+separator
269     text+="MaximumSize="+str(self.SP_MaxSize.value())+separator
270     text+="MinimumSize="+str(self.SP_MinSize.value())+separator
271     text+="MeshGradation="+str(self.SP_Gradation.value())+separator
272     text+="Verbosity="+str(self.SP_Verbosity.value())+separator
273     text+="Memory="+str(self.SP_Memory.value())+separator
274     return str(text)
275
276   def loadResumeData(self, hypothesis, separator="\n"):
277     text=str(hypothesis)
278     self.clean()
279     for slig in reversed(text.split(separator)):
280       lig=slig.strip()
281       #print "load ResumeData",lig
282       if lig=="": continue #skip blanck lines
283       if lig[0]=="#": break
284       try:
285         tit,value=lig.split("=")
286         if tit=="Optimisation":
287           #no need: exlusives QRadioButton
288           #for RB in self.GBOptim.findChildren(QRadioButton,):
289           #  RB.setChecked(False)
290           for RB in self.GBOptim.findChildren(QRadioButton,):
291             if RB.text()==value :
292               RB.setChecked(True)
293               break
294         if tit=="Units":
295           if value=="absolute":
296             self.RB_Absolute.setChecked(True)
297             self.RB_Relative.setChecked(False)
298           else:
299             self.RB_Absolute.setChecked(False)
300             self.RB_Relative.setChecked(True)
301         if tit=="ChordalToleranceDeviation": self.SP_Tolerance.setProperty("text", float(value))
302         if tit=="RidgeDetection": self.CB_Ridge.setChecked(value=="True")
303         if tit=="SplitEdge": self.CB_SplitEdge.setChecked(value=="True")
304         if tit=="PointSmoothing": self.CB_Point.setChecked(value=="True")
305         if tit=="GeometricalApproximation": self.SP_Geomapp.setProperty("value", float(value))
306         if tit=="RidgeAngle": self.SP_Ridge.setProperty("value", float(value))
307         if tit=="MaximumSize": self.SP_MaxSize.setProperty("value", float(value))
308         if tit=="MinimumSize": self.SP_MinSize.setProperty("value", float(value))
309         if tit=="MeshGradation": self.SP_Gradation.setProperty("value", float(value))
310         if tit=="Verbosity": self.SP_Verbosity.setProperty("value", int(float(value)))
311         if tit=="Memory": self.SP_Memory.setProperty("value", float(value))
312       except:
313         QMessageBox.warning(self, "load YAMS Hypothesis", "Problem on '"+lig+"'")
314
315   def PBLoadPressed(self):
316     """load last hypothesis saved in tail of file"""
317     try:
318       f=open(self.paramsFile,"r")
319     except:
320       QMessageBox.warning(self, "File", "Unable to open "+self.paramsFile)
321       return
322     try:
323       text=f.read()
324     except:
325       QMessageBox.warning(self, "File", "Unable to read "+self.paramsFile)
326       return
327     f.close()
328     self.loadResumeData(text, separator="\n")
329
330   def PBLoadHypPressed(self):
331     """load hypothesis saved in Object Browser"""
332     #QMessageBox.warning(self, "load Object Browser YAMS hypothesis", "TODO")
333     import salome
334     from salome.kernel import studyedit
335     from salome.smesh.smeshstudytools import SMeshStudyTools
336     from salome.gui import helper as guihelper
337     from omniORB import CORBA
338
339     mySObject, myEntry = guihelper.getSObjectSelected()
340     if CORBA.is_nil(mySObject) or mySObject==None:
341       QMessageBox.critical(self, "Hypothese", "select an Object Browser YAMS hypothesis")
342       return
343     
344     text=mySObject.GetComment()
345     
346     #a verification
347     if "Optimisation=" not in text:
348       QMessageBox.critical(self, "Load Hypothese", "Object Browser selection not a YAMS Hypothesis")
349       return
350     self.loadResumeData(text, separator=" ; ")
351     return
352     
353   def PBCancelPressed(self):
354     self.close()
355
356   def PBMeshFilePressed(self):
357     fd = QFileDialog(self, "select an existing Mesh file", self.LE_MeshFile.text(), "Mesh-Files (*.mesh);;All Files (*)")
358     if fd.exec_():
359       infile = fd.selectedFiles()[0]
360       self.LE_MeshFile.setText(infile)
361       self.fichierIn=infile.toLatin1()
362       self.MeshIn=""
363       self.LE_MeshSmesh.setText("")
364
365   def setParamsFileName(self):
366     fd = QFileDialog(self, "select a file", self.LE_ParamsFile.text(), "dat Files (*.dat);;All Files (*)")
367     if fd.exec_():
368       infile = fd.selectedFiles()[0]
369       self.LE_ParamsFile.setText(infile)
370       self.paramsFile=infile.toLatin1()
371
372   def meshFileNameChanged(self):
373     self.fichierIn=str(self.LE_MeshFile.text())
374     #print "meshFileNameChanged", self.fichierIn
375     if os.path.exists(self.fichierIn): 
376       self.__selectedMesh=None
377       self.MeshIn=""
378       self.LE_MeshSmesh.setText("")
379       return
380     QMessageBox.warning(self, "Mesh file", "File doesn't exist")
381
382   def meshSmeshNameChanged(self):
383     """only change by GUI mouse selection, otherwise clear"""
384     self.__selectedMesh = None
385     self.MeshIn=""
386     self.LE_MeshSmesh.setText("")
387     self.fichierIn=""
388     return
389
390   def paramsFileNameChanged(self):
391     self.paramsFile=self.LE_ParamsFile.text()
392
393   def PBMeshSmeshPressed(self):
394     from omniORB import CORBA
395     import salome
396     from salome.kernel import studyedit
397     from salome.smesh.smeshstudytools import SMeshStudyTools
398     from salome.gui import helper as guihelper
399     from salome.smesh import smeshBuilder
400     smesh = smeshBuilder.New(salome.myStudy)
401
402     mySObject, myEntry = guihelper.getSObjectSelected()
403     if CORBA.is_nil(mySObject) or mySObject==None:
404       QMessageBox.critical(self, "Mesh", "select an input mesh")
405       return
406     self.smeshStudyTool = SMeshStudyTools()
407     try:
408       self.__selectedMesh = self.smeshStudyTool.getMeshObjectFromSObject(mySObject)
409     except:
410       QMessageBox.critical(self, "Mesh", "select an input mesh")
411       return
412     if CORBA.is_nil(self.__selectedMesh):
413       QMessageBox.critical(self, "Mesh", "select an input mesh")
414       return
415     myName = mySObject.GetName()
416     #print "MeshSmeshNameChanged", myName
417     self.MeshIn=myName
418     self.LE_MeshSmesh.setText(myName)
419     self.LE_MeshFile.setText("")
420     self.fichierIn=""
421
422   def prepareFichier(self):
423     self.fichierIn="/tmp/ForYams_"+str(self.num)+".meshb"
424     self.__selectedMesh.ExportGMF(self.__selectedMesh, self.fichierIn, True)
425
426   def PrepareLigneCommande(self):
427     if self.fichierIn=="" and self.MeshIn=="":
428       QMessageBox.critical(self, "Mesh", "select an input mesh")
429       return False
430     if self.__selectedMesh!=None: self.prepareFichier()
431     if not (os.path.isfile(self.fichierIn)):
432       QMessageBox.critical(self, "File", "unable to read GMF Mesh in "+str(self.fichierIn))
433       return False
434     
435     self.commande="mg-surfopt.exe"
436     
437     for obj in self.GBOptim.findChildren(QRadioButton,):
438       try:
439         if obj.isChecked():
440           self.style=obj.objectName().remove(0,3)
441           self.style.replace("_","-")
442           break
443       except:
444         pass
445       
446     style = self.style.toLatin1()
447     # Translation of old Yams options to new MG-SurfOpt options
448     if   style == "0" :
449       self.commande+= " --optimisation only"
450     elif style == "2" :
451       self.commande+= " --Hausdorff_like yes"
452     elif style == "-1":
453       self.commande+= " --enrich no"
454     elif style == "-2":
455       self.commande+= " --Hausdorff_like yes --enrich no"
456     elif style == "U" :
457       self.commande+= " --uniform_flat_subdivision yes"
458     elif style == "S" :
459       self.commande+= " --sand_paper yes"
460
461     deb=os.path.splitext(self.fichierIn)
462     self.fichierOut=deb[0] + "_surfopt.meshb"
463     
464     tolerance=self.SP_toStr(self.SP_Tolerance)
465     if not self.RB_Absolute.isChecked():
466       tolerance+="r"  
467     self.commande+=" --chordal_error %s"%tolerance
468     
469     if self.CB_Ridge.isChecked()    == False : self.commande+=" --compute_ridges no"
470     if self.CB_Point.isChecked()    == False : self.commande+=" --optimisation no"
471     if self.CB_SplitEdge.isChecked()== True  : self.commande+=" --element_order quadratic"
472     if self.SP_Geomapp.value()      != 0.04  : self.commande+=" --geometric_approximation_angle %f"%self.SP_Geomapp.value()
473     if self.SP_Ridge.value()        != 45.0  : self.commande+=" --ridge_angle %f"%self.SP_Ridge.value()
474     if self.SP_MaxSize.value()      != 100   : self.commande+=" --max_size %f"   %self.SP_MaxSize.value()
475     if self.SP_MinSize.value()      != 5     : self.commande+=" --min_size %f"   %self.SP_MinSize.value()
476     if self.SP_Gradation.value()    != 1.3   : self.commande+=" --gradation %f"  %self.SP_MaxSize.value()
477     if self.SP_Memory.value()       != 0     : self.commande+=" --max_memory %d" %self.SP_Memory.value()
478     if self.SP_Verbosity.value()    != 3     : self.commande+=" --max_memory %d" %self.SP_Verbosity.value()
479
480     self.commande+=" --in "  + self.fichierIn
481     self.commande+=" --out " + self.fichierOut
482     
483     print self.commande
484     return True
485
486   def clean(self):
487     self.RB_0.setChecked(True)
488     #no need: exlusives QRadioButton
489     #self.RB_G.setChecked(False)
490     #self.RB_U.setChecked(False)
491     #self.RB_S.setChecked(False)
492     #self.RB_2.setChecked(False)
493     #self.RB_1.setChecked(False)
494     self.RB_Relative.setChecked(True)
495     #no need: exlusives QRadioButton
496     #self.RB_Absolute.setChecked(False)
497     self.SP_Tolerance.setProperty("text", "10.")
498     self.SP_Geomapp.setProperty("value", 0.04)
499     self.SP_Ridge.setProperty("value", 45.0)
500     self.SP_Gradation.setProperty("value", 1.3)
501     self.CB_Ridge.setChecked(True)
502     self.CB_Point.setChecked(True)
503     self.CB_SplitEdge.setChecked(False)
504     self.SP_MaxSize.setProperty("value", -2.0)
505     self.SP_MinSize.setProperty("value", -2.0)
506     self.SP_Verbosity.setProperty("value", 3)
507     self.SP_Memory.setProperty("value", 0)
508
509 __dialog=None
510 def getDialog():
511   """
512   This function returns a singleton instance of the plugin dialog.
513   c est obligatoire pour faire un show sans parent...
514   """
515   global __dialog
516   if __dialog is None:
517     __dialog = MonYamsPlugDialog()
518   #else :
519   #   __dialog.clean()
520   return __dialog
521
522 #
523 # ==============================================================================
524 # Basic use cases and unit test functions
525 # ==============================================================================
526 #
527 def TEST_MonYamsPlugDialog():
528   import sys
529   from PyQt4.QtGui import QApplication
530   from PyQt4.QtCore import QObject, SIGNAL, SLOT
531   app = QApplication(sys.argv)
532   QObject.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
533
534   dlg=MonYamsPlugDialog()
535   dlg.show()
536   sys.exit(app.exec_())
537
538 if __name__ == "__main__":
539   TEST_MonYamsPlugDialog()
540   pass