1 class HookeCli(cmd.Cmd, object):
3 def __init__(self,frame,list_of_events,events_from_gui,config,drivers):
6 self.prompt = 'hooke: '
7 self.current_list=[] #the playlist we're using
8 self.current=None #the current curve under analysis.
11 The actual hierarchy of the "current curve" is a bit complex:
13 self.current = the lhc.HookeCurve container object of the current curve
14 self.current.curve = the current "real" curve object as defined in the filetype driver class
15 self.current.curve.default_plots() = the default plots of the filetype driver.
17 The plot objects obtained by mean of self.current.curve.default_plots()
18 then undergoes modifications by the plotmanip
19 modifier functions. The modified plot is saved in self.plots and used if needed by other functions.
21 self.pointer=0 #a pointer to navigate the current list
23 #Things that come from outside
24 self.frame=frame #the wx frame we refer to
25 self.list_of_events=list_of_events #a list of wx events we use to interact with the GUI
26 self.events_from_gui=events_from_gui #the Queue object we use to have messages from the GUI
27 self.config=config #the configuration dictionary
28 self.drivers=drivers #the file format drivers
30 #get plot manipulation functions
31 plotmanip_functions=[]
32 for object_name in dir(self):
33 if object_name[0:9]=='plotmanip':
34 plotmanip_functions.append(getattr(self,object_name))
35 #put plotmanips in order
36 self.plotmanip=[None for item in self.config['plotmanips']]
37 for item in plotmanip_functions:
38 namefunction=item.__name__[10:]
39 if namefunction in self.config['plotmanips']:
40 nameindex=self.config['plotmanips'].index(namefunction) #index of function in plotmanips config
41 self.plotmanip[nameindex] = item
46 self.playlist_saved=0 #Did we save the playlist?
47 self.playlist_name='' #Name of playlist
48 self.notes_saved=1 #Did we save the notes?
49 self.notes_filename=None #Name of notes
52 self.outlet=lout.Outlet()
54 #Data that must be saved in the playlist, related to the whole playlist (not individual curves)
55 self.playlist_generics={}
57 #make sure we execute _plug_init() for every command line plugin we import
58 for plugin_name in self.config['plugins']:
60 plugin=__import__(plugin_name)
62 eval('plugin.'+plugin_name+'Commands._plug_init(self)')
63 except AttributeError:
68 #load default list, if possible
69 self.do_loadlist(self.config['defaultlist'])
72 #Everything sending an event should be here
73 def _measure_N_points(self, N, whatset=1):
75 general helper function for N-points measures
77 wx.PostEvent(self.frame,self.list_of_events['measure_points'](num_of_points=N, set=whatset))
80 points=self.frame.events_from_gui.get()
86 def _get_displayed_plot(self,dest=0):
88 returns the currently displayed plot.
90 wx.PostEvent(self.frame, self.list_of_events['get_displayed_plot'](dest=dest))
93 displayed_plot=self.events_from_gui.get()
100 def _send_plot(self,plots):
102 sends a plot to the GUI
104 wx.PostEvent(self.frame, self.list_of_events['plot_graph'](plots=plots))
107 def _find_plotmanip(self, name):
109 returns a plot manipulator function from its name
111 return self.plotmanip[self.config['plotmanips'].index(name)]
113 def _clickize(self, xvector, yvector, index):
115 returns a ClickedPoint() object from an index and vectors of x, y coordinates
119 point.absolute_coords=xvector[index],yvector[index]
120 point.find_graph_coords(xvector,yvector)
128 Sets a local configuration variable
130 Syntax: set [variable] [value]
132 def do_set(self,args):
133 #FIXME: some variables in self.config should be hidden or intelligently configurated...
136 print 'You must specify a variable and a value'
137 print 'Available variables:'
138 print self.config.keys()
140 if args[0] not in self.config.keys():
141 print 'This is not an internal Hooke variable!'
144 #FIXME:we should reload the config file and reset the config value
145 print self.config[args[0]]
148 try: #try to have a numeric value
150 except ValueError: #if it cannot be converted to float, it's None, or a string...
152 if value.lower()=='none':
157 self.config[key]=value
160 def help_printlist(self):
163 Prints the list of curves in the current playlist
167 def do_printlist(self,args):
168 for item in self.current_list:
175 Jumps to a given curve.
177 Syntax: jump {$curve}
179 If the curve is not in the current playlist, it politely asks if we want to add it.
181 def do_jump(self,filename):
183 jumps to the curve with the given filename.
184 if the filename is not in the playlist, it asks if we must add it or not.
188 filename=linp.safeinput('Jump to?')
190 filepath=os.path.abspath(filename)
195 while item_not_found:
198 if self.current_list[c].path == filepath:
200 self.current=self.current_list[self.pointer]
206 #We've found the end of the list.
207 answer=linp.safeinput('Curve not found in playlist. Add it to list?',['y'])
208 if answer.lower()[0]=='y':
210 self.do_addtolist(filepath)
212 print 'Curve file not found.'
214 self.current=self.current_list[-1]
215 self.pointer=(len(current_list)-1)
221 def do_index(self,args):
224 Prints the index of the current curve in the list
228 print self.pointer+1, 'of', len(self.current_list)
234 Go the next curve in the playlist.
235 If we are at the last curve, we come back to the first.
239 def do_next(self,args):
241 self.current.curve.close_all()
243 print 'No curve file loaded, currently!'
244 print 'This should not happen, report to http://code.google.com/p/hooke'
247 if self.pointer == (len(self.current_list)-1):
249 print 'Playlist finished; back to first curve.'
253 self.current=self.current_list[self.pointer]
262 def help_previous(self,args):
265 Go to the previous curve in the playlist.
266 If we are at the first curve, we jump to the last.
270 def do_previous(self,args):
272 self.current.curve.close_all()
274 print 'No curve file loaded, currently!'
275 print 'This should not happen, report to http://code.google.com/p/hooke'
277 if self.pointer == 0:
278 self.pointer=(len(self.current_list)-1)
279 print 'Start of playlist; jump to last curve.'
283 self.current=self.current_list[self.pointer]
290 self.do_previous(args)
293 #PLOT INTERACTION COMMANDS
294 #-------------------------------
298 Plots the current force curve
302 def do_plot(self,args):
303 if self.current.identify(self.drivers) == False:
305 self.plots=self.current.curve.default_plots()
307 self.plots=self.current.curve.default_plots()
309 print 'Unexpected error occurred in do_plot().'
313 #apply the plotmanip functions eventually present
314 nplots=len(self.plots)
317 for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
318 self.plots[c]=function(self.plots[c], self.current)
320 self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
321 self.plots[c].yaxes=self.config['yaxes']
325 self._send_plot(self.plots)
327 def _delta(self, set=1):
329 calculates the difference between two clicked points
331 print 'Click two points'
332 points=self._measure_N_points(N=2, whatset=set)
333 dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
334 dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
335 unitx=self.plots[points[0].dest].units[0]
336 unity=self.plots[points[0].dest].units[1]
337 return dx,unitx,dy,unity
339 def do_delta(self,args):
343 Measures the delta X and delta Y between two points.
347 dx,unitx,dy,unity=self._delta()
348 print str(dx)+' '+unitx
349 print str(dy)+' '+unity
351 def _point(self, set=1):
352 '''calculates the coordinates of a single clicked point'''
354 print 'Click one point'
355 point=self._measure_N_points(N=1, whatset=set)
357 x=point[0].graph_coords[0]
358 y=point[0].graph_coords[1]
359 unitx=self.plots[point[0].dest].units[0]
360 unity=self.plots[point[0].dest].units[1]
361 return x,unitx,y,unity
363 def do_point(self,args):
367 Returns the coordinates of a point on the graph.
371 x,unitx,y,unity=self._point()
372 print str(x)+' '+unitx
373 print str(y)+' '+unity
374 to_dump='point '+self.current.path+' '+str(x)+' '+unitx+', '+str(y)+' '+unity
375 self.outlet.push(to_dump)
378 def do_close(self,args=None):
381 Closes one of the two plots. If no arguments are given, the bottom plot is closed.
383 Syntax: close [top,bottom]
392 close_plot=self.list_of_events['close_plot']
393 wx.PostEvent(self.frame, close_plot(to_close=to_close))
395 def do_show(self,args=None):
400 show_plots=self.list_of_events['show_plots']
401 wx.PostEvent(self.frame, show_plots())
405 #PLOT EXPORT AND MANIPULATION COMMANDS
406 def help_export(self):
409 Saves the current plot as an image file
411 Syntax: export [filename] {plot to export}
413 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
414 and correctly exported. Resolution is (for now) fixed at 150 dpi.
416 If you have a multiple plot, the optional plot to export argument tells Hooke which plot you want to export. If 0, the top plot is exported. If 1, the bottom plot is exported (Exporting both plots is still to implement)
418 def do_export(self,args):
419 #FIXME: the bottom plot doesn't have the title
424 #FIXME: We have to go into the libinput stuff and fix it, for now here's a dummy replacement...
425 #name=linp.safeinput('Filename?',[self.current.path+'.png'])
426 name=raw_input('Filename? ')
433 export_image=self.list_of_events['export_image']
434 wx.PostEvent(self.frame, export_image(name=name, dest=dest))
440 Saves the current curve as a text file
441 Columns are, in order:
442 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
445 Syntax: txt [filename] {plot to export}
447 def do_txt(self,args):
449 def transposed2(lists, defval=0):
451 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
453 (by Zoran Isailovski on the Python Cookbook online)
455 if not lists: return []
456 return map(lambda *row: [elem or defval for elem in row], *lists)
461 filename=linp.safeinput('Filename?',[self.current.path+'.txt'])
463 filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
465 whichplot=int(args[1])
470 outofplot=self.plots[whichplot].vectors
472 print "Plot index out of range."
476 for dataset in self.plots[whichplot].vectors:
477 for i in range(0,len(dataset)):
479 for value in dataset[i]:
480 columns[-1].append(str(value))
482 rows=transposed2(columns, 'nan')
483 rows=[' , '.join(item) for item in rows]
486 txtfile=open(filename,'w+')
487 #Save units of measure in header
488 txtfile.write('X:'+self.plots[whichplot].units[0]+'\n')
489 txtfile.write('Y:'+self.plots[whichplot].units[1]+'\n')
494 #LOGGING, REPORTING, NOTETAKING
497 def do_note_old(self,args):
500 **deprecated**: Use note instead. Will be removed in 0.9
502 Writes or displays a note about the current curve.
503 If [anything] is empty, it displays the note, otherwise it adds a note.
504 The note is then saved in the playlist if you issue a savelist command
506 Syntax: note_old [anything]
510 print self.current_list[self.pointer].notes
512 #bypass UnicodeDecodeError troubles
514 args=args.decode('ascii')
516 args=args.decode('ascii','ignore')
520 self.current_list[self.pointer].notes=args
524 def do_note(self,args):
528 Writes or displays a note about the current curve.
529 If [anything] is empty, it displays the note, otherwise it adds a note.
530 The note is then saved in the playlist if you issue a savelist command.
532 Syntax: note_old [anything]
536 print self.current_list[self.pointer].notes
538 if self.notes_filename == None:
539 if not os.path.exists(os.path.realpath('output')):
541 self.notes_filename=raw_input('Notebook filename? ')
542 self.notes_filename=os.path.join(os.path.realpath('output'),self.notes_filename)
543 title_line='Notes taken at '+time.asctime()+'\n'
544 f=open(self.notes_filename,'a')
548 #bypass UnicodeDecodeError troubles
550 args=args.decode('ascii')
552 args=args.decode('ascii','ignore')
555 self.current_list[self.pointer].notes=args
557 f=open(self.notes_filename,'a+')
558 note_string=(self.current.path+' | '+self.current.notes+'\n')
562 def help_notelog(self):
565 Writes a log of the notes taken during the session for the current
568 Syntax notelog [filename]
570 def do_notelog(self,args):
573 args=linp.safeinput('Notelog filename?',['notelog.txt'])
575 note_lines='Notes taken at '+time.asctime()+'\n'
576 for item in self.current_list:
577 if len(item.notes)>0:
578 #FIXME: log should be justified
579 #FIXME: file path should be truncated...
580 note_string=(item.path+' | '+item.notes+'\n')
581 note_lines+=note_string
587 except IOError, (ErrorNumber, ErrorMessage):
588 print 'Error: notes cannot be saved. Catched exception:'
593 def help_copylog(self):
596 Moves the annotated curves to another directory
598 Syntax copylog [directory]
600 def do_copylog(self,args):
603 args=linp.safeinput('Destination directory?') #TODO default
605 mydir=os.path.abspath(args)
606 if not os.path.isdir(mydir):
607 print 'Destination is not a directory.'
610 for item in self.current_list:
611 if len(item.notes)>0:
613 shutil.copy(item.path, mydir)
614 except (OSError, IOError):
615 print 'Cannot copy file. '+item.path+' Perhaps you gave me a wrong directory?'