diff --git a/Components.py b/Components.py index 7fd2109a..67675542 100644 --- a/Components.py +++ b/Components.py @@ -48,7 +48,8 @@ import ZipManager -from Utilities import replaceAll, GetActiveWindow, printOnStatusBar +from Utilities import replaceAll, GetActiveWindow, printOnStatusBar, install +from Decorators import BuzyCursorNotification from NetManager import Net from which import which @@ -766,9 +767,19 @@ def OnEditor(self, event): if val == wx.ID_YES: ### open with local editor if wx.Platform == '__WXMAC__': - subprocess.call(" ".join(['open',python_path]), shell=True) + subprocess.call(" ".join(['open -a',python_path]), shell=True) elif "wxMSW" in wx.PlatformInfo: - os.startfile(python_path) + ### TODO : select dialog to chose editor (spyder, pyzo, etc..) + try: + import spyder + except ImportError: + if BuzyCursorNotification(install('spyder')): + dial = wx.MessageDialog(mainW, _('You need to restart DEVSimPy to use the Spyder code editor.'), name, wx.OK | wx.ICON_INFORMATION) + val = dial.ShowModal() + else: + subprocess.Popen(['spyder', python_path, '--multithread']) + + #os.startfile(python_path) elif "wxGTK" in wx.PlatformInfo: ### with gnome if os.system('pidof gedit') == 256: diff --git a/Container.py b/Container.py index 8f39e888..bf1eeada 100644 --- a/Container.py +++ b/Container.py @@ -80,6 +80,7 @@ GREEN = '#90ee90' BLACK = '#000000' BLUE = '#add8e6' +ORANGE = '#ffa500' import Components @@ -4544,6 +4545,8 @@ def __init__(self, label = 'QuickScope'): CodeBlock.__init__(self, label, 1, 0) + self.fill = [ORANGE] + ### enable edition on properties panel self.AddAttribute("xlabel") self.AddAttribute("ylabel") @@ -4572,6 +4575,8 @@ def __init__(self, label='DiskGUI'): """ CodeBlock.__init__(self, label, 1, 0) + self.fill = [ORANGE] + def OnLeftDClick(self, event): """ Left Double Click has been appeared. """ diff --git a/Domain/Collector/MessagesCollector.py b/Domain/Collector/MessagesCollector.py index b3283517..9ef46b64 100644 --- a/Domain/Collector/MessagesCollector.py +++ b/Domain/Collector/MessagesCollector.py @@ -35,7 +35,7 @@ def __init__(self, fileName = "result", ext = '.dat', comma = ""): DomainBehavior.__init__(self) ### a way to overcome the random initialization of the fileNam attr directly in the param list of the constructor! - fileName = fileName if fileName != "result" else os.path.join(tempfile.gettempdir(),"result%d"%random.randint(1,100)) + fileName = fileName if fileName != "result" else os.path.join(tempfile.gettempdir(),"result%d"%random.randint(1,100000)) # local copy self.fileName = fileName diff --git a/Domain/Collector/To_Disk.py b/Domain/Collector/To_Disk.py index 70456e03..49f4d006 100644 --- a/Domain/Collector/To_Disk.py +++ b/Domain/Collector/To_Disk.py @@ -50,7 +50,7 @@ def __init__(self, fileName = "result", eventAxis = False, comma = " ", ext = '. QuickScope.__init__(self) ### a way to overcome the random initialization of the fileNam attr directly in the param list of the constructor! - fileName = fileName if fileName!= 'result' else os.path.join(tempfile.gettempdir(),"result%d"%random.randint(1,100)) + fileName = fileName if fileName!= 'result' else os.path.join(tempfile.gettempdir(),"result%d"%random.randint(1,100000)) # local copy self.fileName = fileName diff --git a/Mixins/Savable.py b/Mixins/Savable.py index 836bd846..f77bc352 100644 --- a/Mixins/Savable.py +++ b/Mixins/Savable.py @@ -39,19 +39,22 @@ except: subprocess.run(f'pip install {lib_name}'.split()) -#try: -import yaml -builtins.__dict__['YAML_IMPORT'] = True -#except ImportError as info: -# builtins.__dict__['YAML_IMPORT'] = False -# sys.stdout.write("yaml module was not found! Install it if you want to save model in yaml format.\n") - -#try: -import ruamel.yaml -builtins.__dict__['YAML_IMPORT'] = True -#except ImportError as info: -# builtins.__dict__['YAML_IMPORT'] = False -# sys.stdout.write("ruamel.yaml module was not found! Install it if you want to save model in yaml format.\n") +try: + import yaml + builtins.__dict__['YAML_IMPORT'] = True +except ImportError as info: + builtins.__dict__['YAML_IMPORT'] = False + sys.stdout.write("yaml module was not found! Install it if you want to save model in yaml format.\n") + +try: + import ruamel.yaml as ruamel + builtins.__dict__['YAML_IMPORT'] = True +except ImportError as info: + try: + import ruamel_yaml as ruamel + except ImportError as info: + builtins.__dict__['YAML_IMPORT'] = False + sys.stdout.write("ruamel.yaml module was not found! Install it if you want to save model in yaml format.\n") from tempfile import gettempdir @@ -478,10 +481,10 @@ def Save(self, obj_dumped, fileName = None): assert(fileName.endswith(tuple(DumpYAMLFile.ext))) try: - yaml = ruamel.yaml.YAML() + yaml = ruamel.YAML() yaml.register_class(PickledCollection) with open(fileName, 'w') as yf: - ruamel.yaml.dump(PickledCollection(obj_dumped), stream=yf, default_flow_style=False) + ruamel.dump(PickledCollection(obj_dumped), stream=yf, default_flow_style=False) except Exception as info: tb = traceback.format_exc() sys.stderr.write(_("\nProblem saving: %s -- %s\n")%(str(fileName),str(tb))) @@ -495,10 +498,10 @@ def Load(self, obj_loaded, fileName = None): ## try to open f with compressed mode try: - yaml = ruamel.yaml.YAML() + yaml = ruamel.YAML() yaml.register_class(PickledCollection) with open(fileName, 'r') as yf: - dsp = ruamel.yaml.load(yf, Loader=ruamel.yaml.Loader) + dsp = ruamel.load(yf, Loader=ruamel.Loader) except Exception as info: exc_type, exc_obj, exc_tb = sys.exc_info() diff --git a/PlotGUI.py b/PlotGUI.py index 837d71a9..78992bfd 100644 --- a/PlotGUI.py +++ b/PlotGUI.py @@ -78,27 +78,37 @@ def PlotManager(parent, label, atomicModel, xl, yl): """ ### there is a active simulation thread ? - dyn = True in ['Simulator' in a.getName() for a in threading.enumerate()[1:]] + dyn_flag = True in ['Simulator' in a.getName() for a in threading.enumerate()[1:]] - if atomicModel.fusion: - if dyn: - frame = DynamicPlot(parent, wx.NewIdRef(),_("Plotting %s")%label, atomicModel, xLabel = xl, yLabel = yl) + ### plots are superposed ? + fusion_flag = atomicModel.fusion + + ### data to plot + data = atomicModel if dyn_flag else atomicModel.results + + if dyn_flag: + if fusion_flag: + frame = DynamicPlot(parent, wx.NewIdRef(), _("Plotting %s")%label, atomicModel, xLabel=xl, yLabel=yl) + frame.CenterOnParent() + frame.Show() else: - frame = StaticPlot(parent, wx.NewIdRef(),_("Plotting %s")%label, atomicModel.results, xLabel = xl, yLabel = yl, legend = atomicModel.blockModel.label) - frame.CenterOnParent() - frame.Show() - else: - if dyn: for key in atomicModel.results: - frame = DynamicPlot(parent, wx.NewIdRef(), _("%s on port %s")%(label,str(key)), atomicModel, xLabel = xl, yLabel = yl, iport=key) - frame.CenterOnParent() - frame.Show() + frame = DynamicPlot(parent, wx.NewIdRef(), _("%s on port %s")%(label,str(key)), atomicModel, xLabel = xl, yLabel = yl, iport=key) + frame.CenterOnParent() + frame.Show() + else: + ### values to plot are string (for state for instance) ? + str_data_flag = isinstance(data[0][0][-1], str) if isinstance(data, dict) else isinstance(data[0][-1], str) + + if str_data_flag or fusion_flag: + frame = StaticPlot(parent, wx.NewIdRef(), _("Plotting %s")%label, data, xLabel=xl, yLabel=yl, legend=atomicModel.blockModel.label, fusion=fusion_flag) + frame.CenterOnParent() + frame.Show() else: - for key in atomicModel.results: - frame = StaticPlot(parent, wx.NewIdRef(), _("%s on port %s")%(label,str(key)), atomicModel.results[key], xLabel = xl, yLabel = yl, legend = atomicModel.blockModel.label) + for key in data: + frame = StaticPlot(parent, wx.NewIdRef(), _("%s on port %s")%(label,str(key)), data[key], xLabel = xl, yLabel = yl, legend = atomicModel.blockModel.label) frame.CenterOnParent() frame.Show() - class PlotPanel(wx.Panel): def __init__(self, parent, id=-1, dpi=None, **kwargs): wx.Panel.__init__(self, parent, id=id, **kwargs) @@ -126,7 +136,6 @@ def add(self, name="plot"): page = PlotPanel(self.nb) self.nb.AddPage(page, name) return page.figure - class PlotFrame(wx.Frame): def __init__(self, parent=None, id=wx.NewIdRef(), title="Time Plotting"): """ Constructor. @@ -471,12 +480,10 @@ class StaticPlot(PlotFrame): """ """ - def __init__(self, parent = None, id = wx.NewIdRef(), title = "Time Plotting", data = None, xLabel = 'Time [s]', yLabel = 'Amplitude [A]', typ = 'PlotLine', legend=''): + def __init__(self, parent = None, id = wx.NewIdRef(), title = "Time Plotting", data = None, xLabel = 'Time [s]', yLabel = 'Amplitude [A]', typ = 'PlotLine', legend='', fusion=False): """ @data : [(t,y)...] """ - PlotFrame.__init__(self, parent, id, title) - # local copy self.data = data self.xLabel = xLabel @@ -485,46 +492,98 @@ def __init__(self, parent = None, id = wx.NewIdRef(), title = "Time Plotting", d self.typ = typ self.title = title self.legend = legend + self.fusion_flag = fusion - menu = wx.Menu() + ### if values to plot are string (for state for instance) + ### the frame used to display the data is not PlotFrame. + if (isinstance(data, dict) and isinstance(data[0][0][-1], str)) or (isinstance(data, list) and isinstance(data[0][-1], str)): + wx.Frame.__init__(self, parent, id, title, size=(800, 500), style=wx.DEFAULT_FRAME_STYLE|wx.CLIP_CHILDREN) + self.OnPlotStep() + else: - ### si mode fusion - if isinstance(self.data, dict): - for i in range(len(self.data)): - self.Bind(wx.EVT_MENU,self.OnPlotSpectrum, menu.Append(wx.NewIdRef(), _('Signal %d')%i, _('Spectrum Plot'))) - self.Bind(wx.EVT_MENU,self.OnPlotAllSpectrum, menu.Append(wx.NewIdRef(), _('All'), _('Spectrum Plot'))) + PlotFrame.__init__(self, parent, id, title) - else: - self.Bind(wx.EVT_MENU,self.OnPlotSpectrum, menu.Append(wx.NewIdRef(), _('Signal'), _('Spectrum Plot'))) - self.mainmenu.Append(menu, _('&Spectrum')) + menu = wx.Menu() - menu = wx.Menu() - self.Bind(wx.EVT_MENU,self.OnRMSE, menu.Append(wx.NewIdRef(), _('RMSE'), _('Root Mean Square Error'))) - self.mainmenu.Append(menu, _('&Error')) + ### with fusion + if self.fusion_flag: + for i in range(len(self.data)): + self.Bind(wx.EVT_MENU,self.OnPlotSpectrum, menu.Append(wx.NewIdRef(), _('Signal %d')%i, _('Spectrum Plot'))) + self.Bind(wx.EVT_MENU,self.OnPlotAllSpectrum, menu.Append(wx.NewIdRef(), _('All'), _('Spectrum Plot'))) + + else: + self.Bind(wx.EVT_MENU,self.OnPlotSpectrum, menu.Append(wx.NewIdRef(), _('Signal'), _('Spectrum Plot'))) + self.mainmenu.Append(menu, _('&Spectrum')) + + menu = wx.Menu() + self.Bind(wx.EVT_MENU,self.OnRMSE, menu.Append(wx.NewIdRef(), _('RMSE'), _('Root Mean Square Error'))) + self.mainmenu.Append(menu, _('&Error')) - ### call self.On() - getattr(self,'On%s'%self.typ)() + ### call self.On() + getattr(self,'On%s'%self.typ)() + + def Normalize(self, data): + m = max(a[1] for a in data) + return [(b[0], b[1]/m) for b in data] + + def OnPlotStep(self, event=None)-> None: + """ + """ + data = self.data + + plotter = PlotNotebook(self) + x,y = zip(*data[0]) + axes1 = plotter.add(self.title).gca() + axes1.set_xlabel(_('Time'), fontsize=16) + axes1.set_ylabel(_('Label'), fontsize=16) + axes1.step(x, y, LColour[0], label='Inport 0') + axes1.grid(True) + #axes1.set_title(_("Plotting %s")%label) + + ### with fusion + if self.fusion_flag: + ### if multiple inputs + if len(data) >= 2: + for k,v in data.items(): + if v != data[0]: + x,y = zip(*v) + axes1.step(x, y, LColour[k+1],label='Inport %s'%str(k)) + + ### show legend only of mulitple inputs + axes1.legend() + else: + + ### if multiple inputs + if len(data) >= 2: + for k,v in data.items(): + if v != data[0]: + x,y = zip(*v) + ax = plotter.add(_("%s - Inport %s")%(self.title,str(k))).gca() + ax.set_xlabel(_('Time'), fontsize=16) + ax.set_ylabel(_('Label'), fontsize=16) + ax.step(x, y, LColour[k]) + ax.grid(True) + #axes1.set_title(_("Plotting %s")%label) - def OnPlotLine(self, event=None): + def OnPlotLine(self, event=None)-> None: """ """ data = self.data - ### sans fusion + ### without fusion if isinstance(data, list): data = [(i if self.step else x[0], x[1]) for i,x in enumerate(data)] if self.normalize: - m = max([a[1] for a in data]) - data = [(b[0], b[1]/m) for b in data] + data = self.Normalize(data) line = plot.PolyLine(data, legend = 'Port 0 %s'%self.legend, colour = 'black', width = 1) self.gc = plot.PlotGraphics([line], self.title, self.xLabel, self.yLabel) xMin,xMax,yMin,yMax = get_limit(data) - ### avec fusion (voir attribut _fusion de QuickScope) + ### with fusion (see attribut _fusion of QuickScope) else: L=[] xMin, xMax, yMin, yMax = 0.0,0.0,0.0,0.0 @@ -536,8 +595,7 @@ def OnPlotLine(self, event=None): cc = LColour[0] if self.normalize: - m = max([a[1] for a in dd]) - dd = [(b[0], b[1]/m) for b in dd] + dd = self.Normalize(dd) L.append(plot.PolyLine(dd, legend = 'Port %d %s'%(ind,self.legend), colour = cc, width=1)) @@ -552,13 +610,13 @@ def OnPlotLine(self, event=None): self.client.Draw(self.gc, xAxis = (float(xMin),float(xMax)), yAxis = (float(yMin),float(yMax))) - def OnPlotSquare(self, event=None): + def OnPlotSquare(self, event=None)->None: """ """ data = self.data - ## sans fusion + ## without fusion if isinstance(data, list): ### formatage des données spécifique au square @@ -568,8 +626,7 @@ def OnPlotSquare(self, event=None): data.append(v2) if self.normalize: - m = max([a[1] for a in data]) - data = [(b[0], b[1]/m) for b in data] + data = self.Normalize(data) line = plot.PolyLine(data, legend = 'Port 0 %s'%self.legend, colour = 'black', width = 1) self.gc = plot.PlotGraphics([line], self.title, self.xLabel, self.yLabel) @@ -621,8 +678,7 @@ def OnPlotScatter(self, event=None): ## sans fusion if isinstance(data, list): if self.normalize: - m = max([a[1] for a in data]) - data = [(b[0], b[1]/m) for b in data] + data = self.Normalize(data) markers = plot.PolyMarker(data, colour = LColour[0], marker = Markers[0], size = 1) line = plot.PolyLine(data, legend = 'Port 0 %s'%self.legend, colour = LColour[0], width = 1) @@ -823,13 +879,13 @@ class DynamicPlot(PlotFrame): """ """ - def __init__(self, parent = None, id = wx.NewIdRef(), title = "", atomicModel = None, xLabel = "", yLabel = "", iport=None): + def __init__(self, parent=None, id=wx.NewIdRef(), title="", atomicModel=None, xLabel="", yLabel="", iport=None): """ - @parent : parent class - @id : class id - @title : title of frame - @xLabel : label for x axe - @yLabel : label for y axe + @parent: parent class + @id: class id + @title: title of frame + @xLabel: label for x axe + @yLabel: label for y axe @iport: the number of port when the fusion option is disabled. @atomicModel: QuicScope atomic model used for its data """ @@ -856,6 +912,7 @@ def __init__(self, parent = None, id = wx.NewIdRef(), title = "", atomicModel = break menu = wx.Menu() + ### si mode fusion if self.iport is None: for i in self.atomicModel.results: @@ -865,18 +922,26 @@ def __init__(self, parent = None, id = wx.NewIdRef(), title = "", atomicModel = self.Bind(wx.EVT_MENU, self.OnPlotSpectrum, menu.Append(wx.NewIdRef(), _('Signal %s')%str(self.iport), _('Spectrum Plot'))) self.mainmenu.Append(menu, _('&Spectrum')) - self.timer = wx.Timer(self) + # self.timer = wx.Timer(self) ### DEFAULT_PLOT_DYN_FREQ can be configured in preference-> simulation - self.timer.Start(milliseconds=DEFAULT_PLOT_DYN_FREQ) + # self.timer.Start(milliseconds=DEFAULT_PLOT_DYN_FREQ) - self.Bind(wx.EVT_TIMER, self.OnTimerEvent) - self.Bind(wx.EVT_PAINT, getattr(self, "On%s"%self.type)) + # self.Bind(wx.EVT_TIMER, self.OnTimerEvent) + # self.Bind(wx.EVT_PAINT, getattr(self, "On%s"%self.type)) self.Bind(wx.EVT_CLOSE, self.OnQuit) + getattr(self, "On%s"%self.type)() + def OnTimerEvent(self, event): - self.GetEventHandler().ProcessEvent(wx.PaintEvent( )) + pass + + #self.GetEventHandler().ProcessEvent(wx.PaintEvent()) - def OnPlotLine(self, event): + def Normalize(self, data): + m = max(a[1] for a in data) + return [(b[0], b[1]/m) for b in data] + + def OnPlotLine(self, event=None)->None: """ Plot process depends to the timer event. """ @@ -894,13 +959,12 @@ def OnPlotLine(self, event): data = self.atomicModel.results[self.iport] if self.normalize: - m = max([a[1] for a in data]) - data = [(b[0], b[1]/m) for b in data] + data = self.Normalize(data) line = plot.PolyLine(data, legend = 'Port 0 (%s)'%self.atomicModel.getBlockModel().label, colour = 'black', width = 1) self.gc = plot.PlotGraphics([line], self.title, self.xLabel, self.yLabel) xMin,xMax,yMin,yMax = get_limit(data) - + ### with fusion (look QuickScope attribut _fusion) else: @@ -910,6 +974,7 @@ def OnPlotLine(self, event): L = [] xMin, xMax, yMin, yMax = 0,0,0,0 data_list = list(data.values()) + for ind,dd in enumerate(data_list): #ind = data_list.index(d) try: @@ -919,11 +984,11 @@ def OnPlotLine(self, event): if self.normalize: m = max([a[1] for a in dd]) - ddd = [(b[0], b[1]/m) for b in dd] - - L.append(plot.PolyLine(ddd, legend = 'Port %s (%s)'%(str(list(data.keys())[ind]), label), colour = cc, width=1)) + dd = [(b[0], b[1]/m) for b in dd] + + L.append(plot.PolyLine(dd, legend = 'Port %s (%s)'%(str(list(data.keys())[ind]), label), colour = cc, width=1)) - a,b,c,d = get_limit(ddd) + a,b,c,d = get_limit(dd) if float(a) < float(xMin): xMin=float(a) if float(b) > float(xMax): xMax=float(b) @@ -936,9 +1001,9 @@ def OnPlotLine(self, event): self.client.Draw(self.gc, xAxis = (float(xMin),float(xMax)), yAxis = (float(yMin),float(yMax))) except Exception: sys.stdout.write(_("Error trying to plot")) - - if self.sim_thread is None or not self.sim_thread.isAlive(): - self.timer.Stop() + + # if self.sim_thread is None or not self.sim_thread.isAlive(): + # self.timer.Stop() def OnPlotSquare(self, event): @@ -1037,8 +1102,7 @@ def OnPlotScatter(self, event): data = self.atomicModel.results[self.iport] if self.normalize: - m = max([a[1] for a in data]) - data = [(b[0], b[1]/m) for b in data] + data = self.Normalize(data) markers = plot.PolyMarker(data, colour=LColour[0], marker=Markers[0], size=1) line = plot.PolyLine(data, legend='Port 0 (%s)'%self.atomicModel.getBlockModel().label, colour=LColour[0], width=1) diff --git a/Utilities.py b/Utilities.py index 4f6662f0..43e55a07 100644 --- a/Utilities.py +++ b/Utilities.py @@ -445,7 +445,7 @@ def install(package_to_install, package_to_import=None): dial = wx.MessageDialog(None, _('We find that the package %s is missing. \n\n Do you want to install him using pip?'%(package_to_install)), _('Package Manager'), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) if dial.ShowModal() == wx.ID_YES: - installed = not pip.main(['install', '--user', package_to_install]) + installed = not pip.main(['install', package_to_install]) dial.Destroy() else: installed = False diff --git a/devsimpy.py b/devsimpy.py index 59c6ebf3..45698f4d 100644 --- a/devsimpy.py +++ b/devsimpy.py @@ -1167,6 +1167,7 @@ def OnOpenFile(self, event): wcd = _("DEVSimPy files (*.dsp)|*.dsp|YAML files (*.yaml)|*.yaml|All files (*)|*") home = self.home or os.getenv('USERPROFILE') or os.getenv('HOME') or HOME_PATH if self.openFileList == ['']*NB_OPENED_FILE else self.home or os.path.dirname(self.openFileList[0]) + open_dlg = wx.FileDialog(self, message = _('Choose a file'), defaultDir = home, defaultFile = "", wildcard = wcd, style = wx.OPEN|wx.MULTIPLE|wx.CHANGE_DIR) ### path,diagram dictionary diff --git a/requirements.txt b/requirements.txt index 46b7cc90..52b0e8d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -51,7 +51,7 @@ # plugins\verbose.py: 14 # searchTreeList.py: 1 # wxPyMail.py: 22 -wxPython <= 4.1.0 +wxPython <= 4.1.1 # Components.py: 44 # Container.py: 39 @@ -59,7 +59,7 @@ wxPython <= 4.1.0 # SimulationGUI.py: 35 # SpreadSheet.py: 34 # devsimpy.py: 104 -PyPubSub >= 3.3.0 +PyPubSub <= 3.3.0 # Mixins\Savable.py: 39 PyYAML #>= 5.1.2