Salome HOME
44cce78ca85eb0abf762ec71f5cb46cef81fc9c2
[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_ui 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       self.LE_MeshSmesh.setText("")
406       self.MeshIn=""
407       self.LE_MeshFile.setText("")
408       self.fichierIn=""
409       return
410     self.smeshStudyTool = SMeshStudyTools()
411     try:
412       self.__selectedMesh = self.smeshStudyTool.getMeshObjectFromSObject(mySObject)
413     except:
414       QMessageBox.critical(self, "Mesh", "select an input mesh")
415       return
416     if CORBA.is_nil(self.__selectedMesh):
417       QMessageBox.critical(self, "Mesh", "select an input mesh")
418       return
419     myName = mySObject.GetName()
420     #print "MeshSmeshNameChanged", myName
421     self.MeshIn=myName
422     self.LE_MeshSmesh.setText(myName)
423     self.LE_MeshFile.setText("")
424     self.fichierIn=""
425
426   def prepareFichier(self):
427     self.fichierIn="/tmp/ForSurfOpt_"+str(self.num)+".meshb"
428     self.__selectedMesh.ExportGMF(self.__selectedMesh, self.fichierIn, True)
429
430   def PrepareLigneCommande(self):
431     if self.fichierIn=="" and self.MeshIn=="":
432       QMessageBox.critical(self, "Mesh", "select an input mesh")
433       return False
434     if self.__selectedMesh!=None: self.prepareFichier()
435     if not (os.path.isfile(self.fichierIn)):
436       QMessageBox.critical(self, "File", "unable to read GMF Mesh in "+str(self.fichierIn))
437       return False
438     
439     self.commande="mg-surfopt.exe"
440     
441     for obj in self.GBOptim.findChildren(QRadioButton,):
442       try:
443         if obj.isChecked():
444           self.style=obj.objectName().remove(0,3)
445           self.style.replace("_","-")
446           break
447       except:
448         pass
449       
450     style = self.style.toLatin1()
451     # Translation of old Yams options to new MG-SurfOpt options
452     if   style == "0" :
453       self.commande+= " --optimisation only"
454     elif style == "2" :
455       self.commande+= " --Hausdorff_like yes"
456     elif style == "-1":
457       self.commande+= " --enrich no"
458     elif style == "-2":
459       self.commande+= " --Hausdorff_like yes --enrich no"
460     elif style == "U" :
461       self.commande+= " --uniform_flat_subdivision yes"
462     elif style == "S" :
463       self.commande+= " --sand_paper yes"
464     elif style == "G" :
465       self.commande+= " -O G"  # This option has not been updated to the new option style yet
466
467     deb=os.path.splitext(self.fichierIn)
468     self.fichierOut=deb[0] + "_output.meshb"
469     
470     tolerance=self.SP_toStr(self.SP_Tolerance)
471     if not self.RB_Absolute.isChecked():
472       tolerance+="r"  
473     self.commande+=" --chordal_error %s"%tolerance
474     
475     if self.CB_Ridge.isChecked()    == False : self.commande+=" --compute_ridges no"
476     if self.CB_Point.isChecked()    == False : self.commande+=" --optimisation no"
477     if self.CB_SplitEdge.isChecked()== True  : self.commande+=" --element_order quadratic"
478     if self.SP_Geomapp.value()      != 15.0  : self.commande+=" --geometric_approximation_angle %f"%self.SP_Geomapp.value()
479     if self.SP_Ridge.value()        != 45.0  : self.commande+=" --ridge_angle %f"%self.SP_Ridge.value()
480     if self.SP_MaxSize.value()      != 100   : self.commande+=" --max_size %f"   %self.SP_MaxSize.value()
481     if self.SP_MinSize.value()      != 5     : self.commande+=" --min_size %f"   %self.SP_MinSize.value()
482     if self.SP_Gradation.value()    != 1.3   : self.commande+=" --gradation %f"  %self.SP_MaxSize.value()
483     if self.SP_Memory.value()       != 0     : self.commande+=" --max_memory %d" %self.SP_Memory.value()
484     if self.SP_Verbosity.value()    != 3     : self.commande+=" --verbose %d" %self.SP_Verbosity.value()
485
486     self.commande+=" --in "  + self.fichierIn
487     self.commande+=" --out " + self.fichierOut
488     
489     print self.commande
490     return True
491
492   def clean(self):
493     self.RB_0.setChecked(True)
494     #no need: exlusives QRadioButton
495     #self.RB_G.setChecked(False)
496     #self.RB_U.setChecked(False)
497     #self.RB_S.setChecked(False)
498     #self.RB_2.setChecked(False)
499     #self.RB_1.setChecked(False)
500     self.RB_Relative.setChecked(True)
501     #no need: exlusives QRadioButton
502     #self.RB_Absolute.setChecked(False)
503     self.SP_Tolerance.setProperty("text", "0.001")
504     self.SP_Geomapp.setProperty("value", 15.0)
505     self.SP_Ridge.setProperty("value", 45.0)
506     self.SP_Gradation.setProperty("value", 1.3)
507     self.CB_Ridge.setChecked(True)
508     self.CB_Point.setChecked(True)
509     self.CB_SplitEdge.setChecked(False)
510     self.SP_MaxSize.setProperty("value", -2.0)
511     self.SP_MinSize.setProperty("value", -2.0)
512     self.SP_Verbosity.setProperty("value", 3)
513     self.SP_Memory.setProperty("value", 0)
514     self.PBMeshSmeshPressed()
515     self.TWOptions.setCurrentIndex(0) # Reset current active tab to the first tab
516
517 __dialog=None
518 def getDialog():
519   """
520   This function returns a singleton instance of the plugin dialog.
521   It is mandatory in order to call show witout a parent ...
522   """
523   global __dialog
524   if __dialog is None:
525     __dialog = MonYamsPlugDialog()
526   else :
527     __dialog.clean()
528   return __dialog
529
530 #
531 # ==============================================================================
532 # Basic use cases and unit test functions
533 # ==============================================================================
534 #
535 def TEST_MonYamsPlugDialog():
536   import sys
537   from PyQt4.QtGui import QApplication
538   from PyQt4.QtCore import QObject, SIGNAL, SLOT
539   app = QApplication(sys.argv)
540   QObject.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
541
542   dlg=MonYamsPlugDialog()
543   dlg.show()
544   sys.exit(app.exec_())
545
546 if __name__ == "__main__":
547   TEST_MonYamsPlugDialog()
548   pass