class TemplateView(HasPrivateTraits): """ A feature-based Traits UI plug-in for viewing templates. """ #-- Public Traits ---------------------------------------------------------- # The name of the plugin: name = Str('Template View') # The data context supplying the data to be viewed: context = Instance(ITemplateDataContext, connect='to: data context') # The name of the file containing a template view: file_name = File(drop_file=DropFile( extensions=['.py', '.tv', '.las'], tooltip='Drop a LAS data file, a saved view template ' 'or a Python source file containing a view ' 'template here.'), connect='to: template view') # The template to view: template = Instance(ITemplate) #-- Private Traits --------------------------------------------------------- # The name of the file to save the template in: save_file_name = File # The current data names: data_names = Instance(TemplateDataNames) # The TemplateDataNames or Message being viewed: names_view = Any(no_context) # The name of the most recently loaded template file: template_file_name = File # The template or message being viewed: template_view = Any(no_template) # The options view for the currently active template: options_view = Any(no_options) # The event fired when the user wants to save the template: save_template = Button('Save Template') #-- Traits View Definitions ------------------------------------------------ view = View(VGroup( HGroup(Item('file_name', show_label=False, width=350), TButton('save_template', label='Save Template', enabled_when='template is not None'), group_theme=Theme('@GFB', margins=(-7, -5))), Tabbed(VGroup('8', Label('Data Bindings', item_theme=Theme('@GBB', alignment='center')), Item('names_view', style='custom', resizable=True, editor=InstanceEditor(), export='DockWindowShell', item_theme=Theme('@GFB', margins=(-5, -1))), label='Data Bindings', show_labels=False), Item('template_view', label='Template View', style='custom', resizable=True, editor=InstanceEditor(view='template_view'), export='DockWindowShell'), Item('options_view', label='Template Options', style='custom', resizable=True, editor=InstanceEditor(view='options_view'), export='DockWindowShell'), id='tabbed', dock='horizontal', show_labels=False), ), id='template.test.template_view.TemplateView') #-- Trait Event Handlers --------------------------------------------------- def _context_changed(self, context): """ Handles the 'context' trait being changed. """ if context is None: self.names_view = no_context elif self.template is None: self.names_view = no_template else: self._create_view() def _template_changed(self, template): """ Handles the 'template' trait being changed. """ if self.context is None: self.template_view = no_context else: self._create_view() def _file_name_changed(self, file_name): """ Handles the 'file_name' trait being changed. """ ext = splitext(file_name)[1] if ext == '.py': self._load_python_template(file_name) elif ext == '.tv': self._load_pickled_template(file_name) elif ext == '.las': self._load_las_file(file_name) else: # fixme: Display an informational message here... pass def _save_template_changed(self): """ Handles the user clicking the 'Save Template' button. """ self._save_pickled_template() @on_trait_change('data_names.unresolved_data_names') def _on_unresolved_data_names(self): if len(self.data_names.unresolved_data_names) == 0: self._create_object_view() elif not isinstance(self.template_view, Message): self.template_view = no_bindings self.options_view = no_options @on_trait_change('template.template_mutated?') def _on_template_mutated(self): """ Handles a mutable template changing. """ if self.context is not None: self._create_view() #-- Private Methods -------------------------------------------------------- def _load_python_template(self, file_name): """ Attempts to load a template from a Python source file. """ path, name = split(file_name) sys.path[0:0] = [path] try: ###values = {} module_name, ext = splitext(name) module = __import__(module_name) values = module.__dict__ ###execfile( file_name, values ) template = values.get('template') if template is None: templates = [] for value in list(values.values()): try: if (issubclass(value, Template) and ###(value.__module__ == '__builtin__')): (value.__module__ == module_name)): templates.append(value) except: pass for i, template in enumerate(templates): for t in templates[i + 1:]: if issubclass(template, t): break else: break else: self.template_view = no_template_found return if not isinstance(template, Template): template = template() self.template = template self.template_file_name = file_name except Exception as excp: self.template_view = Message(str(excp)) # Clean up the Python path: del sys.path[0] def _load_pickled_template(self, file_name): """ Attempts to load a template from a pickle. """ # fixme: Implement this...load template from .tv pickle file. fh = None delete = False try: fh = open(file_name, 'rb') file_name = load(fh) path, name = split(file_name) sys.path[0:0] = [path] delete = True module_name, ext = splitext(name) module = __import__(module_name) self.template = load(fh) self.template_file_name = file_name except Exception as excp: import traceback traceback.print_exc() self.template_view = Message(str(excp)) if fh is not None: fh.close() if delete: del sys.path[0] def _load_las_file(self, file_name): """ Creates a data context from the specified LAS file. """ try: self.context = import_log_files(file_name, 'las') except Exception as excp: self.names_view = Message(str(excp)) def _save_pickled_template(self): file_name = self.save_file_name or self.file_name fd = FileDialog(action='save as', default_path=file_name) #wildcard = 'Template files (*.tv)|*.tv|' ) if fd.open() == OK: self.save_file_name = file_name = fd.path fh = None try: fh = open(file_name, 'wb') dump(self.template_file_name, fh, -1) dump(self.template.template_from_object(), fh, -1) except: # fixme: Display an informational message here... import traceback traceback.print_exc() if fh is not None: fh.close() def _create_view(self): """ Begins the process of creating a live view from a template and data context object. """ self.data_names = self.names_view = nv = TemplateDataNames( context=self.context, data_names=self.template.names_from_template()) if len(nv.unresolved_data_names) == 0: self._create_object_view() else: self.template_view = no_bindings def _create_object_view(self): """ Create the object view from the current template. """ self.template.object_from_template() self.template_view = self.options_view = self.template
def traits_view(self): v = View( UItem('plot_panel', editor=InstanceEditor(view='summary_view'), style='custom')) return v
def traits_view(self): multicollect_grp = VGroup( Group( Item('multicollect_counts', label='Counts', tooltip='Number of data points to collect')), UItem('fit_block', editor=EnumEditor(name='fit_blocks')), UItem('fit_block', style='custom', editor=InstanceEditor()), # UItem('active_detectors', # style='custom', # editor=TableEditor( # sortable=False, # reorderable=False, # columns=[ # ObjectColumn(name='label', # editable=False, # label='Det.'), # CheckboxColumn(name='use', label='Use'), # ObjectColumn(name='isotope', # editor=EnumEditor(name='isotopes') # ), # ObjectColumn(name='fit', # editor=EnumEditor(values=[NULL_STR] + FIT_TYPES) # ), # ]) # ), label='Multicollect', show_border=True) baseline_grp = Group( Item('baseline_before', label='Baselines at Start'), Item('baseline_after', label='Baselines at End'), Item('baseline_counts', tooltip='Number of baseline data points to collect', label='Counts'), Item('baseline_detector', label='Detector'), Item( 'baseline_settling_time', label='Delay (s)', tooltip= 'Wait "Delay" seconds after setting magnet to baseline position' ), Item('baseline_mass', label='Mass'), label='Baseline', show_border=True) peak_center_grp = Group( Item('peak_center_before', label='Peak Center at Start'), Item('peak_center_after', label='Peak Center at End'), Item('peak_center_detector', label='Detector', enabled_when='peak_center_before or peak_center_after'), Item('peak_center_isotope', label='Isotope', enabled_when='peak_center_before or peak_center_after'), label='Peak Center', show_border=True) equilibration_grp = Group( Item('eq_time', label='Time (s)'), Item('eq_outlet', label='Ion Pump Valve'), Item( 'eq_delay', label='Delay (s)', tooltip='Wait "Delay" seconds before opening the Inlet Valve'), Item('eq_inlet', label='Inlet Valve'), label='Equilibration', show_border=True) peak_hop_group = VGroup( Group(Item('ncycles'), Item('baseline_ncycles')), HGroup(Spring(springy=False, width=28), Label('position'), Spring(springy=False, width=10), Label('Detectors'), Spring(springy=False, width=188), Label('counts'), spring), UItem('hops', editor=ListEditor(style='custom', editor=InstanceEditor())), label='Peak Hop', visible_when='use_peak_hop', show_border=True) action_editor = TableEditor( columns=[ CheckboxColumn(name='use', width=20), ObjectColumn(name='key', label='Key'), ObjectColumn(name='comparator', label='Comp.'), ObjectColumn(name='criterion', label='Crit.'), ObjectColumn(name='start_count', label='Start'), # ObjectColumn(name='frequency', # label='Freq.' # ), CheckboxColumn(name='resume', label='Resume'), ], deletable=True, row_factory=self._new_action) truncation_editor = TableEditor(columns=[ CheckboxColumn(name='use', width=20), ObjectColumn(name='key', label='Key'), ObjectColumn(name='comparator', label='Comp.'), ObjectColumn(name='criterion', label='Crit.'), ObjectColumn(name='start_count', label='Start'), ], deletable=True, row_factory=self._new_truncation) termination_editor = TableEditor(columns=[ CheckboxColumn(name='use', width=20), ObjectColumn(name='key', label='Key'), ObjectColumn(name='comparator', label='Comp.'), ObjectColumn(name='criterion', label='Crit.'), ObjectColumn(name='start_count', label='Start'), ], deletable=True, row_factory=self._new_termination) cond_grp = VGroup(Group(UItem('actions', editor=action_editor, style='custom'), label='Actions'), Group(UItem('truncations', editor=truncation_editor, style='custom'), label='Truncations'), Group(UItem('terminations', editor=termination_editor, style='custom'), label='Terminations'), label='Conditions') v = View( VGroup( Item('use_peak_hop'), peak_hop_group, Group(multicollect_grp, cond_grp, baseline_grp, peak_center_grp, equilibration_grp, layout='tabbed', visible_when='not use_peak_hop'), )) return v
def traits_view(self): main_grp = Group(UItem('main_view', style='custom', editor=InstanceEditor()), label='Main') teditor = myTabularEditor(adapter=self.isotope_adapter, drag_enabled=False, stretch_last_section=False, editable=False, multi_select=True, selected='selected', dclicked='dclicked', refresh='refresh_needed') ieditor = myTabularEditor(adapter=self.intermediate_adapter, editable=False, drag_enabled=False, stretch_last_section=False, refresh='refresh_needed') isotope_grp = Group(UItem( 'isotopes', editor=teditor, ), UItem('isotopes', editor=ieditor, visible_when='show_intermediate'), label='Isotopes') history_grp = Group(UItem('history_view', style='custom', editor=InstanceEditor()), label='History') experiment_grp = Group(UItem('experiment_view', style='custom', editor=InstanceEditor()), defined_when='experiment_view', label='Experiment') interference_grp = Group(UItem('interference_view', style='custom', editor=InstanceEditor()), defined_when='interference_view', label='Interference') measurement_grp = Group(UItem('measurement_view', style='custom', editor=InstanceEditor()), defined_when='measurement_view', label='Measurement') extraction_grp = Group(UItem('extraction_view', style='custom', editor=InstanceEditor()), defined_when='extraction_view', label='Extraction') snapshot_grp = Group(UItem('snapshot_view', style='custom', editor=InstanceEditor()), defined_when='snapshot_view', label='Snapshot') detector_ic_grp = Group(UItem('detector_ic_view', style='custom', editor=InstanceEditor()), defined_when='detector_ic_view', label='DetectorIC') spectrometer_grp = Group(UItem('spectrometer_view', style='custom', editor=InstanceEditor()), defined_when='spectrometer_view', label='Spectrometer') peak_center_grp = Group(UItem('peak_center_view', style='custom', editor=InstanceEditor()), defined_when='peak_center_view', label='Peak_center') v = View(VGroup( Spring(springy=False, height=-10), Tabbed(main_grp, isotope_grp, history_grp, experiment_grp, extraction_grp, measurement_grp, peak_center_grp, interference_grp, spectrometer_grp, detector_ic_grp, snapshot_grp)), handler=AnalysisViewHandler()) return v
def traits_view(self): ctrl_grp = VGroup( Item('path', show_label=False), Item('highlight_bands', editor=ListEditor(mutable=False, style='custom', editor=InstanceEditor()))) v = View( ctrl_grp, Item('container', show_label=False, editor=ComponentEditor()), # title='Color Inspector', resizable=True, height=800, width=900) return v # def traits_view(self): # lgrp = VGroup(Item('low'), # Item('low', show_label=False, editor=RangeEditor(mode='slider', low=0, high_name='high'))) # hgrp = VGroup(Item('high'), # Item('high', show_label=False, editor=RangeEditor(mode='slider', low_name='low', high=255))) # savegrp = HGroup(Item('save_button', show_label=False), # Item('save_mode', show_label=False)) # ctrlgrp = VGroup( # Item('path', show_label=False), # HGroup(Item('use_threshold'), Item('contrast_equalize'), # HGroup(Item('contrast_low'), Item('contrast_high'), enabled_when='contrast_equalize'), # Item('histogram_equalize') # ), # HGroup(Item('highlight'), Item('highlight_threshold')), # HGroup(spring, # lgrp, # hgrp, # VGroup(savegrp, # Item('calc_area_value', label='Calc. Area For.', # tooltip='Calculate %area for all pixels with this value' # ), # Item('calc_area_threshold', label='Threshold +/- px', # tooltip='bandwidth= calc_value-threshold to calc_value+threshold' # ) # # ) # ), # HGroup(spring, Item('area', style='readonly', width= -200)), # HGroup( # Item('colormap_name_1', show_label=False, # editor=EnumEditor(values=color_map_name_dict.keys())), # spring, # Item('colormap_name_2', show_label=False, # editor=EnumEditor(values=color_map_name_dict.keys()))), # ) # v = View(ctrlgrp, # Item('container', show_label=False, # editor=ComponentEditor()), # # title='Color Inspector', # resizable=True, # height=800, # width=900 # # ) return v
def traits_view(self): v = View(VSplit(UItem('graph', style='custom', height=0.8, editor=InstanceEditor()), UItem('results', height=0.2, editor=TabularEditor(adapter=PeakCenterResultsAdapter())))) return v
class Actor2D(Component): # The version of this class. Used for persistence. __version__ = 0 # The mapper. mapper = Instance(tvtk.AbstractMapper, record=True) # The actor. actor = Instance(tvtk.Prop, record=True) # The actor's property. property = Instance(tvtk.Property2D, record=True) ######################################## # View related traits. # The Actor's view group. _actor_group = Group(Item(name='visibility'), Item(name='height'), Item(name='width'), show_border=True, label='Actor') # The View for this object. view = View( Group(Item(name='actor', style='custom', editor=InstanceEditor(view=View(_actor_group))), show_labels=False, label='Actor'), Group(Item(name='mapper', style='custom', resizable=True), show_labels=False, label='Mapper'), Group(Item(name='property', style='custom', resizable=True), show_labels=False, label='Property'), resizable=True, ) ###################################################################### # `Component` interface ###################################################################### def setup_pipeline(self): """Override this method so that it *creates* its tvtk pipeline. This method is invoked when the object is initialized via `__init__`. Note that at the time this method is called, the tvtk data pipeline will *not* yet be setup. So upstream data will not be available. The idea is that you simply create the basic objects and setup those parts of the pipeline not dependent on upstream sources and filters. """ if self.mapper is None: self.mapper = tvtk.TextMapper() self.actor = tvtk.Actor2D() self.property = self.actor.property def update_pipeline(self): """Override this method so that it *updates* the tvtk pipeline when data upstream is known to have changed. This method is invoked (automatically) when the input fires a `pipeline_changed` event. """ if (len(self.inputs) == 0) or \ (len(self.inputs[0].outputs) == 0): return self.configure_connection(self.mapper, self.inputs[0]) self.render() def update_data(self): """Override this method to do what is necessary when upstream data changes. This method is invoked (automatically) when any of the inputs sends a `data_changed` event. """ # Invoke render to update any changes. self.render() ###################################################################### # Non-public interface. ###################################################################### def _setup_handlers(self, old, new): if old is not None: old.on_trait_change(self.render, remove=True) new.on_trait_change(self.render) def _mapper_changed(self, old, new): # Setup the handlers. self._setup_handlers(old, new) # Setup the inputs to the mapper. if (len(self.inputs) > 0) and (len(self.inputs[0].outputs) > 0): self.configure_connection(new, self.inputs[0]) # Setup the actor's mapper. actor = self.actor if actor is not None: actor.mapper = new self.render() def _actor_changed(self, old, new): # Setup the handlers. self._setup_handlers(old, new) # Set the mapper. mapper = self.mapper if mapper is not None: new.mapper = mapper # Set the property. prop = self.property if prop is not None: new.property = prop # Setup the `actors` trait. self.actors = [new] def _property_changed(self, old, new): # Setup the handlers. self._setup_handlers(old, new) # Setup the actor. actor = self.actor if new is not actor.property: actor.property = new def _foreground_changed_for_scene(self, old, new): # Change the default color for the actor. self.property.color = new self.render() def _scene_changed(self, old, new): super(Actor2D, self)._scene_changed(old, new) self._foreground_changed_for_scene(None, new.foreground)
class ViewHandlerMixin(HasTraits): """ Useful bits for view handlers. """ # the view for the current plot current_plot_view = \ View( HGroup( Item('plot_names_by', editor = TextEditor(), style = "readonly", show_label = False), Item('current_plot', editor = TabListEditor(name = 'plot_names'), style = 'custom', show_label = False))) plot_params_traits = View( Item('plot_params', editor=InstanceEditor(), style='custom', show_label=False)) context = Instance(WorkflowItem) conditions_names = Property(depends_on="context.conditions") previous_conditions_names = Property( depends_on="context.previous_wi.conditions") statistics_names = Property(depends_on="context.statistics") numeric_statistics_names = Property(depends_on="context.statistics") # MAGIC: gets value for property "conditions_names" def _get_conditions_names(self): if self.context and self.context.conditions: return sorted(list(self.context.conditions.keys())) else: return [] # MAGIC: gets value for property "previous_conditions_names" def _get_previous_conditions_names(self): if self.context and self.context.previous_wi and self.context.previous_wi.conditions: return sorted(list(self.context.previous_wi.conditions.keys())) else: return [] # MAGIC: gets value for property "statistics_names" def _get_statistics_names(self): if self.context and self.context.statistics: return sorted(list(self.context.statistics.keys())) else: return [] # MAGIC: gets value for property "numeric_statistics_names" def _get_numeric_statistics_names(self): if self.context and self.context.statistics: return sorted([ x for x in list(self.context.statistics.keys()) if util.is_numeric(self.context.statistics[x]) ]) else: return [] @on_trait_change('context.view_error_trait', dispatch='ui', post_init=True) def _view_trait_error(self): # check if we're getting called on the local or remote process if self.info is None or self.info.ui is None: return for ed in self.info.ui._editors: if ed.name == self.context.view_error_trait: err_state = True else: err_state = False if not ed.label_control: continue item = ed.label_control if not err_state and not hasattr(item, '_ok_color'): continue pal = QtGui.QPalette(item.palette()) # @UndefinedVariable if err_state: setattr(item, '_ok_color', QtGui.QColor(pal.color( item.backgroundRole()))) # @UndefinedVariable pal.setColor(item.backgroundRole(), QtGui.QColor(255, 145, 145)) # @UndefinedVariable item.setAutoFillBackground(True) item.setPalette(pal) else: pal.setColor(item.backgroundRole(), item._ok_color) delattr(item, '_ok_color') item.setAutoFillBackground(False) item.setPalette(pal)
class SwiftConsole(HasTraits): """Traits-defined Swift Console. link : object Serial driver update : bool Update the firmware log_level_filter : str Syslog string, one of "ERROR", "WARNING", "INFO", "DEBUG". """ link = Instance(sbpc.Handler) console_output = Instance(OutputList()) python_console_env = Dict device_serial = Str('') dev_id = Str('') tracking_view = Instance(TrackingView) solution_view = Instance(SolutionView) baseline_view = Instance(BaselineView) skyplot_view = Instance(SkyplotView) observation_view = Instance(ObservationView) networking_view = Instance(SbpRelayView) observation_view_base = Instance(ObservationView) system_monitor_view = Instance(SystemMonitorView) settings_view = Instance(SettingsView) update_view = Instance(UpdateView) ins_view = Instance(INSView) mag_view = Instance(MagView) spectrum_analyzer_view = Instance(SpectrumAnalyzerView) log_level_filter = Enum(list(SYSLOG_LEVELS.values())) """" mode : baseline and solution view - SPP, Fixed or Float num_sat : baseline and solution view - number of satellites port : which port is Swift Device is connected to directory_name : location of logged files json_logging : enable JSON logging csv_logging : enable CSV logging """ pos_mode = Str('') rtk_mode = Str('') ins_status_string = Str('') num_sats_str = Str('') cnx_desc = Str('') age_of_corrections = Str('') uuid = Str('') directory_name = Directory json_logging = Bool(True) csv_logging = Bool(False) show_csv_log = Bool(False) cnx_icon = Str('') heartbeat_count = Int() last_timer_heartbeat = Int() driver_data_rate = Str() solid_connection = Bool(False) csv_logging_button = SVGButton( toggle=True, label='CSV log', tooltip='start CSV logging', toggle_tooltip='stop CSV logging', filename=resource_filename('console/images/iconic/pause.svg'), toggle_filename=resource_filename('console/images/iconic/play.svg'), orientation='vertical', width=2, height=2, ) json_logging_button = SVGButton( toggle=True, label='JSON log', tooltip='start JSON logging', toggle_tooltip='stop JSON logging', filename=resource_filename('console/images/iconic/pause.svg'), toggle_filename=resource_filename('console/images/iconic/play.svg'), orientation='vertical', width=2, height=2, ) paused_button = SVGButton( label='', tooltip='Pause console update', toggle_tooltip='Resume console update', toggle=True, filename=resource_filename('console/images/iconic/pause.svg'), toggle_filename=resource_filename('console/images/iconic/play.svg'), width=8, height=8) clear_button = SVGButton( label='', tooltip='Clear console buffer', filename=resource_filename('console/images/iconic/x.svg'), width=8, height=8) view = View(VSplit( Tabbed(Tabbed(Item('tracking_view', style='custom', label='Signals', show_label=False), Item('skyplot_view', style='custom', label='Sky Plot', show_label=False), label="Tracking"), Item('solution_view', style='custom', label='Solution'), Item('baseline_view', style='custom', label='Baseline'), VSplit( Item('observation_view', style='custom', show_label=False), Item('observation_view_base', style='custom', show_label=False), label='Observations', ), Item('settings_view', style='custom', label='Settings'), Item('update_view', style='custom', label='Update'), Tabbed(Item('system_monitor_view', style='custom', label='System Monitor'), Item('ins_view', style='custom', label='INS'), Item('mag_view', style='custom', label='Magnetometer'), Item('networking_view', label='Networking', style='custom', show_label=False), Item('spectrum_analyzer_view', label='Spectrum Analyzer', style='custom'), label='Advanced', show_labels=False), show_labels=False), VGroup( VGroup( HGroup( Spring(width=4, springy=False), Item('paused_button', show_label=False, padding=0, width=8, height=8), Item('clear_button', show_label=False, width=8, height=8), Item('', label='Console Log', emphasized=True), Item('csv_logging_button', emphasized=True, show_label=False, visible_when='show_csv_log', width=12, height=-30, padding=0), Item('json_logging_button', emphasized=True, show_label=False, width=12, height=-30, padding=0), Item( 'directory_name', show_label=False, springy=True, tooltip= 'Choose location for file logs. Default is home/SwiftNav.', height=-25, enabled_when='not(json_logging or csv_logging)', editor_args={'auto_set': True}), UItem( 'log_level_filter', style='simple', padding=0, height=8, show_label=True, tooltip= 'Show log levels up to and including the selected level of severity.\nThe CONSOLE log level is always visible.' ), ), Item('console_output', style='custom', editor=InstanceEditor(), height=125, show_label=False, full_size=True), ), HGroup( Spring(width=4, springy=False), Item('', label='Port:', emphasized=True, tooltip='Interface for communicating with Swift device'), Item('cnx_desc', show_label=False, style='readonly'), Item('', label='Pos:', emphasized=True, tooltip='Device Position Mode: SPS, DGNSS, or RTK'), Item('pos_mode', show_label=False, style='readonly'), Item('', label='RTK:', emphasized=True, tooltip='Device RTK Mode: Float or Fixed'), Item('rtk_mode', show_label=False, style='readonly'), Item('', label='Sats:', emphasized=True, tooltip='Number of satellites used in solution'), Item('num_sats_str', padding=2, show_label=False, style='readonly'), Item('', label='Corr Age:', emphasized=True, tooltip= 'Age of corrections (-- means invalid / not present)'), Item('age_of_corrections', padding=2, show_label=False, style='readonly'), Item('', label='INS:', emphasized=True, tooltip='INS Status String'), Item('ins_status_string', padding=2, show_label=False, style='readonly', width=6), Spring(springy=True), Item('driver_data_rate', style='readonly', show_label=False), Item('cnx_icon', show_label=False, padding=0, width=8, height=8, visible_when='solid_connection', springy=False, editor=ImageEditor( allow_clipping=False, image=ImageResource( resource_filename( 'console/images/iconic/arrows_blue.png')))), Item('cnx_icon', show_label=False, padding=0, width=8, height=8, visible_when='not solid_connection', springy=False, editor=ImageEditor( allow_clipping=False, image=ImageResource( resource_filename( 'console/images/iconic/arrows_grey.png')))), Spring(width=4, height=-2, springy=False), ), Spring(height=1, springy=False), ), ), icon=icon, resizable=True, width=800, height=600, handler=ConsoleHandler(), title=CONSOLE_TITLE) def print_message_callback(self, sbp_msg, **metadata): try: encoded = sbp_msg.payload.encode('ascii', 'ignore') for eachline in reversed(encoded.split('\n')): self.console_output.write_level( eachline, str_to_log_level(eachline.split(':')[0])) except UnicodeDecodeError as e: print("Error encoding msg_print: {}".format(e)) def log_message_callback(self, sbp_msg, **metadata): encoded = sbp_msg.text.decode('utf8') for eachline in reversed(encoded.split('\n')): self.console_output.write_level(eachline, sbp_msg.level) def ext_event_callback(self, sbp_msg, **metadata): e = MsgExtEvent(sbp_msg) print( 'External event: %s edge on pin %d at wn=%d, tow=%d, time qual=%s' % ("Rising" if (e.flags & (1 << 0)) else "Falling", e.pin, e.wn, e.tow, "good" if (e.flags & (1 << 1)) else "unknown")) def cmd_resp_callback(self, sbp_msg, **metadata): r = MsgCommandResp(sbp_msg) print("Received a command response message with code {0}".format( r.code)) def _paused_button_fired(self): self.console_output.paused = not self.console_output.paused def _log_level_filter_changed(self): """ Takes log level enum and translates into the mapped integer. Integer stores the current filter value inside OutputList. """ self.console_output.log_level_filter = str_to_log_level( self.log_level_filter) def _clear_button_fired(self): self.console_output.clear() def _directory_name_changed(self): if self.baseline_view and self.solution_view: self.baseline_view.directory_name_b = self.directory_name self.solution_view.directory_name_p = self.directory_name self.solution_view.directory_name_v = self.directory_name if self.observation_view and self.observation_view_base: self.observation_view.dirname = self.directory_name self.observation_view_base.dirname = self.directory_name def update_on_heartbeat(self, sbp_msg, **metadata): self.heartbeat_count += 1 def check_heartbeat(self): # if our heartbeat hasn't changed since the last timer interval the connection must have dropped if self.heartbeat_count == self.last_timer_heartbeat and self.heartbeat_count != 0: self.solid_connection = False self.ins_status_string = "None" self.pos_mode = "None" self.ins_mode = "None" self.num_sats_str = EMPTY_STR self.last_timer_heartbeat = self.heartbeat_count else: self.solid_connection = True self.last_timer_heartbeat = self.heartbeat_count bytes_diff = self.driver.bytes_read_since(self.last_driver_bytes_read) # 1024 bytes per KiloByte self.driver_data_rate = "{0:.2f} KB/s".format( (bytes_diff) / (HEARTBEAT_CHECK_PERIOD_SECONDS * 1024)) self.last_driver_bytes_read = self.driver.total_bytes_read # if we aren't getting heartbeats and we have at some point gotten heartbeats, we don't have # a good connection and we should age out the data from the views if not self.solid_connection or self.driver_data_rate == 0: return # -- grab current time to use in logic current_time = monotonic() # --- determining which mode, llh or baseline, to show in the status bar --- llh_display_mode = "None" llh_num_sats = 0 llh_is_rtk = False baseline_display_mode = "None" ins_status_string = EMPTY_STR # determine the latest llh solution mode if self.solution_view and (current_time - self.solution_view.last_stime_update) < 1: llh_solution_mode = self.solution_view.last_pos_mode llh_display_mode = pos_mode_dict.get(llh_solution_mode, EMPTY_STR) if llh_solution_mode > 0 and self.solution_view.last_soln: llh_num_sats = self.solution_view.last_soln.n_sats llh_is_rtk = (llh_solution_mode in RTK_MODES) if getattr(self.solution_view, 'ins_used', False) and llh_solution_mode != DR_MODE: llh_display_mode += "+INS" # determine the latest baseline solution mode if (self.baseline_view and self.settings_view and self.settings_view.dgnss_enabled and (current_time - self.baseline_view.last_btime_update) < 1): baseline_solution_mode = self.baseline_view.last_mode baseline_display_mode = rtk_mode_dict.get(baseline_solution_mode, EMPTY_STR) # if baseline solution mode is empty and POS mode is RTK, get the RTK mode from pos if baseline_display_mode not in rtk_mode_dict.values() and llh_is_rtk: baseline_display_mode = rtk_mode_dict.get(llh_solution_mode) # determine the latest INS mode if self.solution_view and ( current_time - self.solution_view.last_ins_status_receipt_time) < 1: ins_flags = self.solution_view.ins_status_flags ins_mode = ins_flags & 0x7 ins_type = (ins_flags >> 29) & 0x7 odo_status = (ins_flags >> 8) & 0x3 # ins_status has bug in odo. # If it says no odo, check if we have had tics in last 10 seconds from INS_UPDATES if odo_status != 1: if (current_time - self.solution_view.last_odo_update_time) < 10: odo_status = 1 ins_error = (ins_flags >> 4) & 0xF if ins_error != 0: ins_status_string = ins_error_dict.get(ins_error, "Unk Error") else: ins_status_string = ins_type_dict.get(ins_type, "unk") + "-" ins_status_string += ins_mode_dict.get(ins_mode, "unk") if odo_status == 1: ins_status_string += "+Odo" # get age of corrections from baseline view if self.baseline_view: if (self.baseline_view.age_corrections is not None and (current_time - self.baseline_view.last_age_corr_receipt_time) < 1): self.age_of_corrections = "{0} s".format( self.baseline_view.age_corrections) else: self.age_of_corrections = EMPTY_STR # populate modes and #sats on status bar self.ins_status_string = ins_status_string self.rtk_mode = baseline_display_mode self.pos_mode = llh_display_mode self.num_sats_str = "{}".format(llh_num_sats) # --- end of status bar mode determination section --- if self.settings_view: # for auto populating surveyed fields self.settings_view.lat = self.solution_view.latitude self.settings_view.lon = self.solution_view.longitude self.settings_view.alt = self.solution_view.altitude def _csv_logging_button_action(self): if self.csv_logging and self.baseline_view.logging_b and self.solution_view.logging_p and self.solution_view.logging_v: print("Stopped CSV logging") self.csv_logging = False self.baseline_view.logging_b = False self.solution_view.logging_p = False self.solution_view.logging_v = False else: print("Started CSV logging at %s" % self.directory_name) self.csv_logging = True self.baseline_view.logging_b = True self.solution_view.logging_p = True self.solution_view.logging_v = True def _start_json_logging(self, override_filename=None): if override_filename: filename = override_filename else: filename = time.strftime("swift-gnss-%Y%m%d-%H%M%S.sbp.json", time.localtime()) filename = os.path.normpath( os.path.join(self.directory_name, filename)) self.logger = s.get_logger(True, filename, self.expand_json) self.forwarder = sbpc.Forwarder(self.link, self.logger) self.forwarder.start() if self.settings_view: self.settings_view._settings_read_all() def _stop_json_logging(self): fwd = self.forwarder fwd.stop() self.logger.flush() self.logger.close() def _json_logging_button_action(self): if self.first_json_press and self.json_logging: print( "JSON Logging initiated via CMD line. Please press button again to stop logging" ) elif self.json_logging: self._stop_json_logging() self.json_logging = False print("Stopped JSON logging") else: self._start_json_logging() self.json_logging = True self.first_json_press = False def _json_logging_button_fired(self): if not os.path.exists(self.directory_name) and not self.json_logging: print( "The selected logging directory does not exist and will be created." ) self._json_logging_button_action() def _csv_logging_button_fired(self): if not os.path.exists(self.directory_name) and not self.csv_logging: print( "The selected logging directory does not exist and will be created." ) self._csv_logging_button_action() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.console_output.close() def __init__(self, link, driver, update, log_level_filter, error=False, cnx_desc=None, json_logging=False, show_csv_log=False, log_dirname=None, override_filename=None, log_console=False, connection_info=None, expand_json=False, hide_legend=False): self.error = error self.cnx_desc = cnx_desc self.connection_info = connection_info self.show_csv_log = show_csv_log self.dev_id = cnx_desc self.num_sats_str = EMPTY_STR self.mode = '' self.ins_status_string = "None" self.forwarder = None self.age_of_corrections = '--' self.expand_json = expand_json # if we have passed a logfile, we set our directory to it override_filename = override_filename self.last_status_update_time = 0 self.last_driver_bytes_read = 0 self.driver = driver if log_dirname: self.directory_name = log_dirname if override_filename: override_filename = os.path.join(log_dirname, override_filename) else: self.directory_name = swift_path # Start swallowing sys.stdout and sys.stderr self.console_output = OutputList(tfile=log_console, outdir=self.directory_name) sys.stdout = self.console_output self.console_output.write("Console: " + CONSOLE_VERSION + " starting...") if not error: sys.stderr = self.console_output self.log_level_filter = log_level_filter self.console_output.log_level_filter = str_to_log_level( log_level_filter) try: self.link = link self.link.add_callback(self.print_message_callback, SBP_MSG_PRINT_DEP) self.link.add_callback(self.log_message_callback, SBP_MSG_LOG) self.link.add_callback(self.ext_event_callback, SBP_MSG_EXT_EVENT) self.link.add_callback(self.cmd_resp_callback, SBP_MSG_COMMAND_RESP) self.link.add_callback(self.update_on_heartbeat, SBP_MSG_HEARTBEAT) self.dep_handler = DeprecatedMessageHandler(link) settings_read_finished_functions = [] self.tracking_view = TrackingView(self.link, legend_visible=(not hide_legend)) self.solution_view = SolutionView(self.link, dirname=self.directory_name) self.baseline_view = BaselineView(self.link, dirname=self.directory_name) self.skyplot_view = SkyplotView(self.link, self.tracking_view) self.observation_view = ObservationView( self.link, name='Local', relay=False, dirname=self.directory_name, tracking_view=self.tracking_view) self.observation_view_base = ObservationView( self.link, name='Remote', relay=True, dirname=self.directory_name) self.system_monitor_view = SystemMonitorView(self.link) self.update_view = UpdateView(self.link, download_dir=swift_path, prompt=update, connection_info=self.connection_info) self.ins_view = INSView(self.link) self.mag_view = MagView(self.link) self.spectrum_analyzer_view = SpectrumAnalyzerView(self.link) settings_read_finished_functions.append( self.update_view.compare_versions) self.networking_view = SbpRelayView(self.link) self.json_logging = json_logging self.csv_logging = False self.first_json_press = True if json_logging: self._start_json_logging(override_filename) self.json_logging = True # we set timer interval to 1200 milliseconds because we expect a heartbeat each second self.timer_cancel = call_repeatedly(HEARTBEAT_CHECK_PERIOD_SECONDS, self.check_heartbeat) # Once we have received the settings, update device_serial with # the Swift serial number which will be displayed in the window # title. This callback will also update the header route as used # by the networking view. def update_serial(): mfg_id = None try: self.uuid = self.settings_view.settings['system_info'][ 'uuid'].value mfg_id = self.settings_view.settings['system_info'][ 'serial_number'].value except KeyError: pass if mfg_id: self.device_serial = 'PK' + str(mfg_id) skip_settings_read = False if 'mode' in self.connection_info: if self.connection_info['mode'] == 'file': skip_settings_read = True settings_read_finished_functions.append(update_serial) self.settings_view = SettingsView(self.link, settings_read_finished_functions, skip_read=skip_settings_read) self.update_view.settings = self.settings_view.settings self.python_console_env = { 'send_message': self.link, 'link': self.link, } self.python_console_env.update( self.tracking_view.python_console_cmds) self.python_console_env.update( self.solution_view.python_console_cmds) self.python_console_env.update( self.baseline_view.python_console_cmds) self.python_console_env.update( self.skyplot_view.python_console_cmds) self.python_console_env.update( self.observation_view.python_console_cmds) self.python_console_env.update( self.networking_view.python_console_cmds) self.python_console_env.update( self.system_monitor_view.python_console_cmds) self.python_console_env.update( self.update_view.python_console_cmds) self.python_console_env.update(self.ins_view.python_console_cmds) self.python_console_env.update(self.mag_view.python_console_cmds) self.python_console_env.update( self.settings_view.python_console_cmds) self.python_console_env.update( self.spectrum_analyzer_view.python_console_cmds) except: # noqa import traceback traceback.print_exc() if self.error: os._exit(1)
class TableFilterEditor(HasTraits): """ An editor that manages table filters. """ #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # TableEditor this editor is associated with editor = Instance(TableEditor) # The list of filters filters = List(TableFilter) # The list of available templates from which filters can be created templates = Property(List(TableFilter), depends_on='filters') # The currently selected filter template selected_template = Instance(TableFilter) # The currently selected filter selected_filter = Instance(TableFilter, allow_none=True) # The view to use for the current filter selected_filter_view = Property(depends_on='selected_filter') # Buttons for add/removing filters add_button = Button('New') remove_button = Button('Delete') # The default view for this editor view = View(Group(Group(Group(Item('add_button', enabled_when='selected_template'), Item('remove_button', enabled_when='len(templates) > 1 and ' \ 'selected_filter is not None'), orientation='horizontal', show_labels=False), Label('Base filter for new filters:'), Item('selected_template', editor=EnumEditor(name='templates')), Item('selected_filter', style='custom', editor=EnumEditor(name='filters', mode='list')), show_labels=False), Item('selected_filter', width=0.75, style='custom', editor=InstanceEditor(view_name='selected_filter_view')), id='TableFilterEditorSplit', show_labels=False, layout='split', orientation='horizontal'), id='traitsui.qt4.table_editor.TableFilterEditor', buttons=[ 'OK', 'Cancel' ], kind='livemodal', resizable=True, width=800, height=400, title='Customize filters') #--------------------------------------------------------------------------- # Private methods: #--------------------------------------------------------------------------- #-- Trait Property getter/setters ------------------------------------------ @cached_property def _get_selected_filter_view(self): view = None if self.selected_filter: model = self.editor.model index = model.mapToSource(model.index(0, 0)) if index.isValid(): obj = self.editor.items()[index.row()] else: obj = None view = self.selected_filter.edit_view(obj) return view @cached_property def _get_templates(self): templates = [f for f in self.editor.factory.filters if f.template] templates.extend(self.filters) return templates #-- Trait Change Handlers -------------------------------------------------- def _editor_changed(self): self.filters = [ f.clone_traits() for f in self.editor.factory.filters if not f.template ] self.selected_template = self.templates[0] def _add_button_fired(self): """ Create a new filter based on the selected template and select it. """ new_filter = self.selected_template.clone_traits() new_filter.template = False new_filter.name = new_filter._name = 'New filter' self.filters.append(new_filter) self.selected_filter = new_filter def _remove_button_fired(self): """ Delete the currently selected filter. """ if self.selected_template == self.selected_filter: self.selected_template = self.templates[0] index = self.filters.index(self.selected_filter) del self.filters[index] if index < len(self.filters): self.selected_filter = self.filters[index] else: self.selected_filter = None @on_trait_change('selected_filter:name') def _update_filter_list(self): """ A hack to make the EnumEditor watching the list of filters refresh their text when the name of the selected filter changes. """ filters = self.filters self.filters = [] self.filters = filters
def init(self, parent): """Finishes initializing the editor by creating the underlying toolkit widget.""" factory = self.factory self.columns = factory.columns[:] if factory.table_view_factory is not None: self.table_view = factory.table_view_factory(editor=self) if factory.source_model_factory is not None: self.source_model = factory.source_model_factory(editor=self) if factory.model_factory is not None: self.model = factory.model_factory(editor=self) # Create the table view and model self.model.setDynamicSortFilter(True) self.model.setSourceModel(self.source_model) self.table_view.setModel(self.model) # Create the vertical header context menu and connect to its signals self.header_menu = QtGui.QMenu(self.table_view) signal = QtCore.SIGNAL('triggered()') insertable = factory.row_factory is not None and not factory.auto_add if factory.editable: if insertable: action = self.header_menu.addAction('Insert new item') QtCore.QObject.connect(action, signal, self._on_context_insert) if factory.deletable: action = self.header_menu.addAction('Delete item') QtCore.QObject.connect(action, signal, self._on_context_remove) if factory.reorderable: if factory.editable and (insertable or factory.deletable): self.header_menu.addSeparator() self.header_menu_up = self.header_menu.addAction('Move item up') QtCore.QObject.connect(self.header_menu_up, signal, self._on_context_move_up) self.header_menu_down = self.header_menu.addAction( 'Move item down') QtCore.QObject.connect(self.header_menu_down, signal, self._on_context_move_down) # Create the empty space context menu and connect its signals self.empty_menu = QtGui.QMenu(self.table_view) action = self.empty_menu.addAction('Add new item') QtCore.QObject.connect(action, signal, self._on_context_append) # When sorting is enabled, the first column is initially displayed with # the triangle indicating it is the sort index, even though no sorting # has actually been done. Sort here for UI/model consistency. if self.factory.sortable and not self.factory.reorderable: self.model.sort(0, QtCore.Qt.AscendingOrder) # Connect to the mode specific selection handler and select the first # row/column/cell. Do this before creating the edit_view to make sure # that it has a valid item to use when constructing its view. smodel = self.table_view.selectionModel() signal = QtCore.SIGNAL( 'selectionChanged(QItemSelection, QItemSelection)') mode_slot = getattr(self, '_on_%s_selection' % factory.selection_mode) QtCore.QObject.connect(smodel, signal, mode_slot) self.table_view.setCurrentIndex(self.model.index(0, 0)) # Create the toolbar if necessary if factory.show_toolbar and len(factory.filters) > 0: main_view = QtGui.QWidget() layout = QtGui.QVBoxLayout(main_view) layout.setContentsMargins(0, 0, 0, 0) self.toolbar_ui = self.edit_traits( parent=parent, kind='subpanel', view=View(Group(Item('filter{View}', editor=factory._filter_editor), Item('filter_summary{Results}', style='readonly'), spring, orientation='horizontal'), resizable=True)) self.toolbar_ui.parent = self.ui layout.addWidget(self.toolbar_ui.control) layout.addWidget(self.table_view) else: main_view = self.table_view # Create auxillary editor and encompassing splitter if necessary mode = factory.selection_mode if (factory.edit_view == ' ') or not mode in ('row', 'rows'): self.control = main_view else: self.control = QtGui.QSplitter(QtCore.Qt.Vertical) self.control.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.control.addWidget(main_view) self.control.setStretchFactor(0, 2) # Create the row editor below the table view editor = InstanceEditor(view=factory.edit_view, kind='subpanel') self._ui = self.edit_traits( parent=self.control, kind='subpanel', view=View(Item('selected_row', style='custom', editor=editor, show_label=False, resizable=True, width=factory.edit_view_width, height=factory.edit_view_height), resizable=True, handler=factory.edit_view_handler)) self._ui.parent = self.ui self.control.addWidget(self._ui.control) self.control.setStretchFactor(1, 1) # Connect to the click and double click handlers signal = QtCore.SIGNAL('clicked(QModelIndex)') QtCore.QObject.connect(self.table_view, signal, self._on_click) signal = QtCore.SIGNAL('doubleClicked(QModelIndex)') QtCore.QObject.connect(self.table_view, signal, self._on_dclick) # Make sure we listen for 'items' changes as well as complete list # replacements self.context_object.on_trait_change(self.update_editor, self.extended_name + '_items', dispatch='ui') # Listen for changes to traits on the objects in the list self.context_object.on_trait_change(self.refresh_editor, self.extended_name + '.-', dispatch='ui') # Listen for changes on column definitions self.on_trait_change(self._update_columns, 'columns', dispatch='ui') self.on_trait_change(self._update_columns, 'columns_items', dispatch='ui') # Set up the required externally synchronized traits is_list = (mode in ('rows', 'columns', 'cells')) self.sync_value(factory.click, 'click', 'to') self.sync_value(factory.dclick, 'dclick', 'to') self.sync_value(factory.columns_name, 'columns', is_list=True) self.sync_value(factory.selected, 'selected', is_list=is_list) self.sync_value(factory.selected_indices, 'selected_indices', is_list=is_list) self.sync_value(factory.filter_name, 'filter', 'from') self.sync_value(factory.filtered_indices, 'filtered_indices', 'to') self.sync_value(factory.update_filter_name, 'update_filter', 'from') self.auto_size = self.factory.auto_size # Initialize the ItemDelegates for each column self._update_columns()
class DataBlockTablePlot(HasTraits): # the ParametersTable holding the actual array dataset = Instance(DataBlockTable) # a local reference to the data array in the dataset, for convenience selected = Property(Any, depends_on='dataset.selected') # ArrayPlotData for plot plot_data = Instance(ArrayPlotData, ()) # Chaco Plot instance plot = Instance(Plot) traits_view = View( VGroup( Item('dataset', show_label=False, style='custom', editor=InstanceEditor()), Item('plot', show_label=False, editor=ComponentEditor(), width=0.25, height=0.35), ), width=0.60, height=0.60, resizable=True, title='Parameters Plot', ) def cview(self): return View( VGroup( Item('dataset', show_label=False, style='custom', editor=InstanceEditor()), Item('plot', show_label=False, editor=ComponentEditor(), width=0.25, height=0.35), ), width=0.60, height=0.60, resizable=True, title='Raw Data Plot', ) @cached_property def _get_selected(self): return self.dataset.selected @on_trait_change('selected') def _data_source_change(self): """Handler for changes of ther selected table row """ if self.selected is None: return x = np.linspace(0, \ len(self.selected['raw_data']), \ len(self.selected['raw_data'])) self.plot_data.set_data('x', x) self.plot_data.set_data('y', self.selected['raw_data']) def _plot_data_default(self): """ This creates a list of ArrayPlotData instances, one for each species. """ plot_data = ArrayPlotData() plot_data.set_data('x', []) plot_data.set_data('y', []) return plot_data def _plot_default(self): """ This creates the default value for the plot. """ # create the main plot object plot = Plot(self.plot_data) plot.plot(('x', 'y'), type='line', name='raw data', color='lightgreen') # add the additional information plot.title = 'Row Data' plot.x_axis.title = 'Samples' plot.y_axis.title = 'Amplitude' # tools for basic interactivity plot.tools.append(PanTool(plot)) plot.tools.append(ZoomTool(plot)) plot.tools.append(DragZoom(plot, drag_button="right")) return plot
class SourceWidget(Component): # The version of this class. Used for persistence. __version__ = 0 # The actual poly data source widget. widget = Instance(tvtk.ThreeDWidget, record=True) # Specifies the updation mode of the poly_data attribute. There # are three modes: 1) 'interactive' -- the poly_data attribute is # updated as the widget is interacted with, 2) 'semi-interactive' # -- poly_data attribute is updated when the traits of the widget # change and when the widget interaction is complete, 3) # 'non-interactive' -- poly_data is updated only explicitly at # users request by calling `object.update_poly_data`. update_mode = Trait('interactive', TraitPrefixList(['interactive', 'semi-interactive', 'non-interactive']), desc='the speed at which the poly data is updated') # A list of predefined glyph sources that can be used. widget_list = List(tvtk.Object, record=False) # The poly data that the widget manages. poly_data = Instance(tvtk.PolyData, args=()) ######################################## # Private traits. _first = Bool(True) _busy = Bool(False) _unpickling = Bool(False) ######################################## # View related traits. view = View(Group(Item(name='widget', style='custom', resizable=True, editor=InstanceEditor(name='widget_list')), label='Source Widget', show_labels=False, ), resizable=True, ) ###################################################################### # `Base` interface ###################################################################### def __get_pure_state__(self): d = super(SourceWidget, self).__get_pure_state__() for attr in ('poly_data', '_unpickling', '_first', '_busy'): d.pop(attr, None) return d def __set_pure_state__(self, state): self._unpickling = True # First create all the allowed widgets in the widget_list attr. handle_children_state(self.widget_list, state.widget_list) # Now set their state. set_state(self, state, first=['widget_list'], ignore=['*']) # Set the widget attr depending on value saved. m = [x.__class__.__name__ for x in self.widget_list] w_c_name = state.widget.__metadata__['class_name'] w = self.widget = self.widget_list[m.index(w_c_name)] # Set the input. if len(self.inputs) > 0: w.input = self.inputs[0].outputs[0] # Fix for the point widget. if w_c_name == 'PointWidget': w.place_widget() # Set state of rest of the attributes ignoring the widget_list. set_state(self, state, ignore=['widget_list']) # Some widgets need some cajoling to get their setup right. w.update_traits() if w_c_name == 'PlaneWidget': w.origin = state.widget.origin w.normal = state.widget.normal w.update_placement() w.get_poly_data(self.poly_data) elif w_c_name == 'SphereWidget': # XXX: This hack is necessary because the sphere widget # does not update its poly data even when its ivars are # set (plus it does not have an update_placement method # which is a bug). So we force this by creating a similar # sphere source and copy its output. s = tvtk.SphereSource(center=w.center, radius=w.radius, theta_resolution=w.theta_resolution, phi_resolution=w.phi_resolution, lat_long_tessellation=True) s.update() self.poly_data.shallow_copy(s.output) else: w.get_poly_data(self.poly_data) self._unpickling = False # Set the widgets trait so that the widget is rendered if needed. self.widgets = [w] ###################################################################### # `Component` interface ###################################################################### def setup_pipeline(self): """Override this method so that it *creates* the tvtk pipeline. This method is invoked when the object is initialized via `__init__`. Note that at the time this method is called, the tvtk data pipeline will *not* yet be setup. So upstream data will not be available. The idea is that you simply create the basic objects and setup those parts of the pipeline not dependent on upstream sources and filters. You should also set the `actors` attribute up at this point. """ # Setup the glyphs. sources = [tvtk.SphereWidget(theta_resolution=8, phi_resolution=6), tvtk.LineWidget(clamp_to_bounds=False), tvtk.PlaneWidget(), tvtk.PointWidget(outline=False, x_shadows=False, y_shadows=False, z_shadows=False), ] self.widget_list = sources # The 'widgets' trait is set in the '_widget_changed' handler. self.widget = sources[0] for s in sources: self._connect(s) def update_pipeline(self): """Override this method so that it *updates* the tvtk pipeline when data upstream is known to have changed. This method is invoked (automatically) when any of the inputs sends a `pipeline_changed` event. """ if len(self.inputs) == 0: return inp = self.inputs[0].outputs[0] w = self.widget w.input = inp if self._first: w.place_widget() self._first = False # If the dataset is effectively 2D switch to using the line # widget since that works best. b = inp.bounds l = [(b[1]-b[0]), (b[3]-b[2]), (b[5]-b[4])] max_l = max(l) for i, x in enumerate(l): if x/max_l < 1.0e-6: w = self.widget = self.widget_list[1] w.clamp_to_bounds = True w.align = ['z_axis', 'z_axis', 'y_axis'][i] break # Set our output. w.get_poly_data(self.poly_data) self.outputs = [self.poly_data] self.pipeline_changed = True def update_data(self): """Override this method so that it flushes the vtk pipeline if that is necessary. This method is invoked (automatically) when any of the inputs sends a `data_changed` event. """ self.data_changed = True ###################################################################### # `SourceWidget` interface ###################################################################### def update_poly_data(self): self.widget.get_poly_data(self.poly_data) ###################################################################### # Non-public traits. ###################################################################### def _widget_changed(self, value): # If we are being unpickled do nothing. if self._unpickling: return if value not in self.widget_list: classes = [o.__class__ for o in self.widget_list] vc = value.__class__ self._connect(value) if vc in classes: self.widget_list[classes.index(vc)] = value else: self.widget_list.append(value) recorder = self.recorder if recorder is not None: idx = self.widget_list.index(value) name = recorder.get_script_id(self) lhs = '%s.widget'%name rhs = '%s.widget_list[%d]'%(name, idx) recorder.record('%s = %s'%(lhs, rhs)) if len(self.inputs) > 0: value.input = self.inputs[0].outputs[0] value.place_widget() value.on_trait_change(self.render) self.widgets = [value] def _update_mode_changed(self, value): if value in ['interactive', 'semi-interactive']: self.update_poly_data() self.render() def _on_interaction_event(self, obj, event): if (not self._busy) and (self.update_mode == 'interactive'): self._busy = True self.update_poly_data() self._busy = False def _on_widget_trait_changed(self): if (not self._busy) and (self.update_mode != 'non-interactive'): self._busy = True # This render call forces any changes to the trait to be # rendered only then will updating the poly data make # sense. self.render() self.update_poly_data() self._busy = False def _on_alignment_set(self): w = self.widget w.place_widget() w.update_traits() def _connect(self, obj): """Wires up all the event handlers.""" obj.add_observer('InteractionEvent', self._on_interaction_event) if isinstance(obj, tvtk.PlaneWidget): obj.on_trait_change(self._on_alignment_set, 'normal_to_x_axis') obj.on_trait_change(self._on_alignment_set, 'normal_to_y_axis') obj.on_trait_change(self._on_alignment_set, 'normal_to_z_axis') elif isinstance(obj, tvtk.LineWidget): obj.on_trait_change(self._on_alignment_set, 'align') # Setup the widgets colors. fg = (1,1,1) if self.scene is not None: fg = self.scene.foreground self._setup_widget_colors(obj, fg) obj.on_trait_change(self._on_widget_trait_changed) obj.on_trait_change(self.render) def _setup_widget_colors(self, widget, color): trait_names = widget.trait_names() props = [x for x in trait_names if 'property' in x and 'selected' not in x] sel_props = [x for x in trait_names if 'property' in x and 'selected' in x] for p in props: setattr(getattr(widget, p), 'color', color) setattr(getattr(widget, p), 'line_width', 2) for p in sel_props: # Set the selected color to 'red'. setattr(getattr(widget, p), 'color', (1,0,0)) setattr(getattr(widget, p), 'line_width', 2) self.render() def _foreground_changed_for_scene(self, old, new): # Change the default color for the actor. for w in self.widget_list: self._setup_widget_colors(w, new) self.render() def _scene_changed(self, old, new): super(SourceWidget, self)._scene_changed(old, new) self._foreground_changed_for_scene(None, new.foreground)
def init(self, parent): """Finishes initializing the editor by creating the underlying toolkit widget.""" factory = self.factory self.filter = factory.filter columns = factory.columns[:] if (len(columns) == 0) and (len(self.value) > 0): columns = [ ObjectColumn(name=name) for name in self.value[0].editable_traits() ] self.columns = columns if factory.table_view_factory is not None: self.table_view = factory.table_view_factory(editor=self) if factory.source_model_factory is not None: self.source_model = factory.source_model_factory(editor=self) if factory.model_factory is not None: self.model = factory.model_factory(editor=self) # Create the table view and model self.model.setDynamicSortFilter(True) self.model.setSourceModel(self.source_model) self.table_view.setModel(self.model) # Create the vertical header context menu and connect to its signals self.header_menu = QtGui.QMenu(self.table_view) insertable = factory.row_factory is not None if factory.editable: if insertable: action = self.header_menu.addAction("Insert new item") action.triggered.connect(self._on_context_insert) if factory.deletable: action = self.header_menu.addAction("Delete item") action.triggered.connect(self._on_context_remove) if factory.reorderable: if factory.editable and (insertable or factory.deletable): self.header_menu.addSeparator() self.header_menu_up = self.header_menu.addAction("Move item up") self.header_menu_up.triggered.connect(self._on_context_move_up) self.header_menu_down = self.header_menu.addAction( "Move item down" ) self.header_menu_down.triggered.connect(self._on_context_move_down) # Create the empty space context menu and connect its signals self.empty_menu = QtGui.QMenu(self.table_view) action = self.empty_menu.addAction("Add new item") action.triggered.connect(self._on_context_append) # When sorting is enabled, the first column is initially displayed with # the triangle indicating it is the sort index, even though no sorting # has actually been done. Sort here for UI/model consistency. if self.factory.sortable and not self.factory.reorderable: self.model.sort(0, QtCore.Qt.AscendingOrder) # Connect to the mode specific selection handler and select the first # row/column/cell. Do this before creating the edit_view to make sure # that it has a valid item to use when constructing its view. smodel = self.table_view.selectionModel() mode_slot = getattr(self, "_on_%s_selection" % factory.selection_mode) smodel.selectionChanged.connect(mode_slot) self.table_view.setCurrentIndex(self.model.index(0, 0)) # Create the toolbar if necessary if factory.show_toolbar and len(factory.filters) > 0: main_view = QtGui.QWidget() layout = QtGui.QVBoxLayout(main_view) layout.setContentsMargins(0, 0, 0, 0) self.toolbar_ui = self.edit_traits( parent=parent, kind="subpanel", view=View( Group( Item("filter{View}", editor=factory._filter_editor), Item("filter_summary{Results}", style="readonly"), spring, orientation="horizontal", ), resizable=True, ), ) self.toolbar_ui.parent = self.ui layout.addWidget(self.toolbar_ui.control) layout.addWidget(self.table_view) else: main_view = self.table_view # Create auxiliary editor and encompassing splitter if necessary mode = factory.selection_mode if (factory.edit_view == " ") or mode not in {"row", "rows"}: self.control = main_view else: if factory.orientation == "horizontal": self.control = QtGui.QSplitter(QtCore.Qt.Horizontal) else: self.control = QtGui.QSplitter(QtCore.Qt.Vertical) self.control.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding ) self.control.addWidget(main_view) self.control.setStretchFactor(0, 2) # Create the row editor below the table view editor = InstanceEditor(view=factory.edit_view, kind="subpanel") self._ui = self.edit_traits( parent=self.control, kind="subpanel", view=View( Item( "selected_row", style="custom", editor=editor, show_label=False, resizable=True, width=factory.edit_view_width, height=factory.edit_view_height, ), resizable=True, handler=factory.edit_view_handler, ), ) self._ui.parent = self.ui self.control.addWidget(self._ui.control) self.control.setStretchFactor(1, 1) # Connect to the click and double click handlers self.table_view.clicked.connect(self._on_click) self.table_view.doubleClicked.connect(self._on_dclick) # Make sure we listen for 'items' changes as well as complete list # replacements self.context_object.on_trait_change( self.update_editor, self.extended_name + "_items", dispatch="ui" ) # Listen for changes to traits on the objects in the list self.context_object.on_trait_change( self.refresh_editor, self.extended_name + ".-", dispatch="ui" ) # Listen for changes on column definitions self.on_trait_change(self._update_columns, "columns", dispatch="ui") self.on_trait_change( self._update_columns, "columns_items", dispatch="ui" ) # Set up the required externally synchronized traits is_list = mode in ("rows", "columns", "cells") self.sync_value(factory.click, "click", "to") self.sync_value(factory.dclick, "dclick", "to") self.sync_value(factory.columns_name, "columns", is_list=True) self.sync_value(factory.selected, "selected", is_list=is_list) self.sync_value( factory.selected_indices, "selected_indices", is_list=is_list ) self.sync_value(factory.filter_name, "filter", "from") self.sync_value(factory.filtered_indices, "filtered_indices", "to") self.sync_value(factory.update_filter_name, "update_filter", "from") self.auto_size = self.factory.auto_size # Initialize the ItemDelegates for each column self._update_columns()
def traits_view(self): cali_grp = VGroup(UItem( 'tray_manager.calibrate', enabled_when='stage_manager.stage_map_name', editor=ButtonEditor(label_value='tray_manager.calibration_step')), HGroup( Readonly('tray_manager.x', format_str='%0.3f'), Readonly('tray_manager.y', format_str='%0.3f')), Readonly('tray_manager.rotation', format_str='%0.3f'), Readonly('tray_manager.scale', format_str='%0.4f'), Readonly('tray_manager.error', format_str='%0.2f'), UItem('tray_manager.calibrator', style='custom', editor=InstanceEditor()), CustomLabel('tray_manager.calibration_help', color='green', height=75, width=300), show_border=True, label='Calibration') # c_grp = VGroup(HGroup(Item('setpoint'), # UItem('setpoint_readback')), # UItem('setpoint_readback', # enabled_when='0', # editor=RangeEditor(mode='slider', # low_name='setpoint_readback_min', # high_name='setpoint_readback_max')), # # label='Controller', show_border=True) c_grp = VGroup(Item('setpoint'), VGroup(UItem('setpoint_readback', editor=LCDEditor())), label='Controller', show_border=True) d_grp = VGroup( Item('stage_manager.calibrated_position_entry', label='Hole'), Item('stage_manager.sample_linear_holder.position', editor=RangeEditor( mode='slider', low_name='stage_manager.sample_linear_holder.min_value', high_name='stage_manager.sample_linear_holder.max_value', )), HGroup( UItem('pane.dump_sample_button', tooltip='Complete sample dumping procedure'), icon_button_editor('pane.funnel_up_button', 'arrow_up', enabled_when='pane.funnel_up_enabled', editor_kw={'label': 'Funnel Up'}), icon_button_editor('pane.funnel_down_button', 'arrow_down', enabled_when='pane.funnel_down_enabled', editor_kw={'label': 'Funnel Down'}), spring), UItem('dumper_canvas', editor=ComponentEditor()), # UItem('pane.lower_funnel_button', enabled_when='stage_manager.funnel.min_limit'), # UItem('pane.raise_funnel_button', enabled_when='stage_manager.funnel.max_limit'), label='Dumper', show_border=True) v = View(VGroup(c_grp, HGroup(Tabbed(d_grp, cali_grp)))) return v
class Text(Module): # The version of this class. Used for persistence. __version__ = 0 # The tvtk TextActor. actor = Instance(tvtk.TextActor, allow_none=False, record=True) # The property of the axes (color etc.). property = Property(record=True) # The text to be displayed. Note that this should really be `Str` # but wxGTK only returns unicode. text = Str('Text', desc='the text to be displayed') # The x-position of this actor. x_position = Float(0.0, desc='the x-coordinate of the text') # The y-position of this actor. y_position = Float(0.0, desc='the y-coordinate of the text') # The z-position of this actor. z_position = Float(0.0, desc='the z-coordinate of the text') # Shadow the positions as ranges for 2D. Simply using a RangeEditor # does not work as it resets the 3D positions to 1 when the dialog is # loaded. _x_position_2d = Range(0., 1., 0., enter_set=True, auto_set=False, desc='the x-coordinate of the text') _y_position_2d = Range(0., 1., 0., enter_set=True, auto_set=False, desc='the y-coordinate of the text') # 3D position position_in_3d = Bool( False, desc='whether the position of the object is given in 2D or in 3D') # The width of the text. width = Range(0.0, 1.0, 0.4, enter_set=True, auto_set=False, desc='the width of the text as a fraction of the viewport') input_info = PipelineInfo(datasets=['any'], attribute_types=['any'], attributes=['any']) ######################################## # The view of this object. if VTK_VER > '5.1': _text_actor_group = Group(Item(name='visibility'), Item(name='text_scale_mode'), Item(name='alignment_point'), Item(name='minimum_size'), Item(name='maximum_line_height'), show_border=True, label='Text Actor') else: _text_actor_group = Group(Item(name='visibility'), Item(name='scaled_text'), Item(name='alignment_point'), Item(name='minimum_size'), Item(name='maximum_line_height'), show_border=True, label='Text Actor') _position_group_2d = Group(Item(name='_x_position_2d', label='X position'), Item(name='_y_position_2d', label='Y position'), visible_when='not position_in_3d') _position_group_3d = Group(Item(name='x_position', label='X', springy=True), Item(name='y_position', label='Y', springy=True), Item(name='z_position', label='Z', springy=True), show_border=True, label='Position', orientation='horizontal', visible_when='position_in_3d') view = View(Group(Group(Item(name='text'), Item(name='position_in_3d'), _position_group_2d, _position_group_3d, Item(name='width', enabled_when='object.actor.scaled_text'), ), Group(Item(name='actor', style='custom', editor=\ InstanceEditor(view=View(_text_actor_group)) ), show_labels=False), label='TextActor', show_labels=False ), Group(Item(name='_property', style='custom', resizable=True), label='TextProperty', show_labels=False), ) ######################################## # Private traits. _updating = Bool(False) _property = Instance(tvtk.TextProperty) ###################################################################### # `object` interface ###################################################################### def __set_pure_state__(self, state): self._updating = True state_pickler.set_state(self, state, first=['actor'], ignore=['_updating']) self._updating = False ###################################################################### # `Module` interface ###################################################################### def setup_pipeline(self): """Override this method so that it *creates* the tvtk pipeline. This method is invoked when the object is initialized via `__init__`. Note that at the time this method is called, the tvtk data pipeline will *not* yet be setup. So upstream data will not be available. The idea is that you simply create the basic objects and setup those parts of the pipeline not dependent on upstream sources and filters. You should also set the `actors` attribute up at this point. """ actor = self.actor = tvtk.TextActor(input=str(self.text)) if VTK_VER > '5.1': actor.trait_set(text_scale_mode='prop', width=0.4, height=1.0) else: actor.trait_set(scaled_text=True, width=0.4, height=1.0) c = actor.position_coordinate c.trait_set(coordinate_system='normalized_viewport', value=(self.x_position, self.y_position, 0.0)) c = actor.position2_coordinate c.trait_set(coordinate_system='normalized_viewport') self._property.opacity = 1.0 self._text_changed(self.text) self._width_changed(self.width) self._shadow_positions(True) def update_pipeline(self): """Override this method so that it *updates* the tvtk pipeline when data upstream is known to have changed. This method is invoked (automatically) when any of the inputs sends a `pipeline_changed` event. """ self.pipeline_changed = True def update_data(self): """Override this method so that it flushes the vtk pipeline if that is necessary. This method is invoked (automatically) when any of the inputs sends a `data_changed` event. """ # Just set data_changed, the component should do the rest. self.data_changed = True ###################################################################### # Non-public interface ###################################################################### def _text_changed(self, value): actor = self.actor if actor is None: return if self._updating: return actor.input = str(value) self.render() def _shadow_positions(self, value): self.sync_trait('x_position', self, '_x_position_2d', remove=(not value)) self.sync_trait('y_position', self, '_y_position_2d', remove=(not value)) if not value: self._x_position_2d = self.x_position self._y_position_2d = self.y_position def _position_in_3d_changed(self, value): if value: self.actor.position_coordinate.coordinate_system = 'world' self.actor.position2_coordinate.coordinate_system = 'world' else: self.actor.position2_coordinate.coordinate_system=\ 'normalized_viewport' self.actor.position_coordinate.coordinate_system=\ 'normalized_viewport' x = self.x_position y = self.y_position if x < 0: x = 0 elif x > 1: x = 1 if y < 0: y = 0 elif y > 1: y = 1 self.trait_set(x_position=x, y_position=y, trait_change_notify=False) self._shadow_positions(not value) self._change_position() self.actor._width_changed(self.width, self.width) self.pipeline_changed = True def _change_position(self): """ Callback for _x_position, _y_position, and z_position. """ actor = self.actor if actor is None: return if self._updating: return x = self.x_position y = self.y_position z = self.z_position if self.position_in_3d: actor.position_coordinate.value = x, y, z else: actor.position = x, y self.render() _x_position_changed = _change_position _y_position_changed = _change_position _z_position_changed = _change_position def _width_changed(self, value): actor = self.actor if actor is None: return if self._updating: return actor.width = value self.render() def _update_traits(self): self._updating = True try: actor = self.actor self.text = actor.input pos = actor.position self.x_position, self.y_position = pos self.width = actor.width finally: self._updating = False def _get_property(self): return self._property def _actor_changed(self, old, new): if old is not None: for obj in (old, self._property): obj.on_trait_change(self.render, remove=True) old.on_trait_change(self._update_traits, remove=True) self._property = new.text_property for obj in (new, self._property): obj.on_trait_change(self.render) new.on_trait_change(self._update_traits) self.actors = [new] self.render() def _foreground_changed_for_scene(self, old, new): # Change the default color for the actor. self.property.color = new self.render() def _scene_changed(self, old, new): super(Text, self)._scene_changed(old, new) self._foreground_changed_for_scene(None, new.foreground)
class SceneModel(TVTKScene): ######################################## # TVTKScene traits. light_manager = Property picker = Property ######################################## # SceneModel traits. # A convenient dictionary based interface to add/remove actors and widgets. # This is similar to the interface provided for the ActorEditor. actor_map = Dict() # This is used primarily to implement the add_actor/remove_actor methods. actor_list = List() # The actual scene being edited. scene_editor = Instance(TVTKScene) do_render = Event() # Fired when this is activated. activated = Event() # Fired when this widget is closed. closing = Event() # This exists just to mirror the TVTKWindow api. scene = Property ################################### # View related traits. # Render_window's view. _stereo_view = Group(Item(name='stereo_render'), Item(name='stereo_type'), show_border=True, label='Stereo rendering', ) # The default view of this object. default_view = View(Group( Group(Item(name='background'), Item(name='foreground'), Item(name='parallel_projection'), Item(name='disable_render'), Item(name='off_screen_rendering'), Item(name='jpeg_quality'), Item(name='jpeg_progressive'), Item(name='magnification'), Item(name='anti_aliasing_frames'), ), Group(Item(name='render_window', style='custom', visible_when='object.stereo', editor=InstanceEditor(view=View(_stereo_view)), show_label=False), ), label='Scene'), Group(Item(name='light_manager', style='custom', editor=InstanceEditor(), show_label=False), label='Lights') ) ################################### # Private traits. # Used by the editor to determine if the widget was enabled or not. enabled_info = Dict() def __init__(self, parent=None, **traits): """ Initializes the object. """ # Base class constructor. We call TVTKScene's super here on purpose. # Calling TVTKScene's init will create a new window which we do not # want. super(TVTKScene, self).__init__(**traits) self.control = None ###################################################################### # TVTKScene API. ###################################################################### def render(self): """ Force the scene to be rendered. Nothing is done if the `disable_render` trait is set to True.""" self.do_render = True def add_actors(self, actors): """ Adds a single actor or a tuple or list of actors to the renderer.""" if hasattr(actors, '__iter__'): self.actor_list.extend(actors) else: self.actor_list.append(actors) def remove_actors(self, actors): """ Removes a single actor or a tuple or list of actors from the renderer.""" my_actors = self.actor_list if hasattr(actors, '__iter__'): for actor in actors: my_actors.remove(actor) else: my_actors.remove(actors) # Conevenience methods. add_actor = add_actors remove_actor = remove_actors def add_widgets(self, widgets, enabled=True): """Adds widgets to the renderer. """ if not hasattr(widgets, '__iter__'): widgets = [widgets] for widget in widgets: self.enabled_info[widget] = enabled self.add_actors(widgets) def remove_widgets(self, widgets): """Removes widgets from the renderer.""" if not hasattr(widgets, '__iter__'): widgets = [widgets] self.remove_actors(widgets) for widget in widgets: del self.enabled_info[widget] def reset_zoom(self): """Reset the camera so everything in the scene fits.""" if self.scene_editor is not None: self.scene_editor.reset_zoom() def save(self, file_name, size=None, **kw_args): """Saves rendered scene to one of several image formats depending on the specified extension of the filename. If an additional size (2-tuple) argument is passed the window is resized to the specified size in order to produce a suitably sized output image. Please note that when the window is resized, the window may be obscured by other widgets and the camera zoom is not reset which is likely to produce an image that does not reflect what is seen on screen. Any extra keyword arguments are passed along to the respective image format's save method. """ self._check_scene_editor() self.scene_editor.save(file_name, size, **kw_args) def save_ps(self, file_name): """Saves the rendered scene to a rasterized PostScript image. For vector graphics use the save_gl2ps method.""" self._check_scene_editor() self.scene_editor.save_ps(file_name) def save_bmp(self, file_name): """Save to a BMP image file.""" self._check_scene_editor() self.scene_editor.save_bmp(file_name) def save_tiff(self, file_name): """Save to a TIFF image file.""" self._check_scene_editor() self.scene_editor.save_tiff(file_name) def save_png(self, file_name): """Save to a PNG image file.""" self._check_scene_editor() self.scene_editor.save_png(file_name) def save_jpg(self, file_name, quality=None, progressive=None): """Arguments: file_name if passed will be used, quality is the quality of the JPEG(10-100) are valid, the progressive arguments toggles progressive jpegs.""" self._check_scene_editor() self.scene_editor.save_jpg(file_name, quality, progressive) def save_iv(self, file_name): """Save to an OpenInventor file.""" self._check_scene_editor() self.scene_editor.save_iv(file_name) def save_vrml(self, file_name): """Save to a VRML file.""" self._check_scene_editor() self.scene_editor.save_vrml(file_name) def save_oogl(self, file_name): """Saves the scene to a Geomview OOGL file. Requires VTK 4 to work.""" self._check_scene_editor() self.scene_editor.save_oogl(file_name) def save_rib(self, file_name, bg=0, resolution=None, resfactor=1.0): """Save scene to a RenderMan RIB file. Keyword Arguments: file_name -- File name to save to. bg -- Optional background option. If 0 then no background is saved. If non-None then a background is saved. If left alone (defaults to None) it will result in a pop-up window asking for yes/no. resolution -- Specify the resolution of the generated image in the form of a tuple (nx, ny). resfactor -- The resolution factor which scales the resolution. """ self._check_scene_editor() self.scene_editor.save_rib(file_name, bg, resolution, resfactor) def save_wavefront(self, file_name): """Save scene to a Wavefront OBJ file. Two files are generated. One with a .obj extension and another with a .mtl extension which contains the material proerties. Keyword Arguments: file_name -- File name to save to """ self._check_scene_editor() self.scene_editor.save_wavefront(file_name) def save_gl2ps(self, file_name, exp=None): """Save scene to a vector PostScript/EPS/PDF/TeX file using GL2PS. If you choose to use a TeX file then note that only the text output is saved to the file. You will need to save the graphics separately. Keyword Arguments: file_name -- File name to save to. exp -- Optionally configured vtkGL2PSExporter object. Defaults to None and this will use the default settings with the output file type chosen based on the extention of the file name. """ self._check_scene_editor() self.scene_editor.save_gl2ps(file_name, exp) def get_size(self): """Return size of the render window.""" self._check_scene_editor() return self.scene_editor.get_size() def set_size(self, size): """Set the size of the window.""" self._check_scene_editor() self.scene_editor.set_size(size) def _update_view(self, x, y, z, vx, vy, vz): """Used internally to set the view.""" if self.scene_editor is not None: self.scene_editor._update_view(x, y, z, vx, vy, vz) def _check_scene_editor(self): if self.scene_editor is None: msg = """ This method requires that there be an active scene editor. To do this, you will typically need to invoke:: object.edit_traits() where object is the object that contains the SceneModel. """ raise SceneModelError(msg) def _scene_editor_changed(self, old, new): if new is None: self._renderer = None self._renwin = None self._interactor = None else: self._renderer = new._renderer self._renwin = new._renwin self._interactor = new._interactor def _get_picker(self): """Getter for the picker.""" se = self.scene_editor if se is not None and hasattr(se, 'picker'): return se.picker return None def _get_light_manager(self): """Getter for the light manager.""" se = self.scene_editor if se is not None: return se.light_manager return None ###################################################################### # SceneModel API. ###################################################################### def _get_scene(self): """Getter for the scene property.""" return self
class UpdateView(HasTraits): piksi_stm_vers = String('Waiting for Piksi to send settings...', width=COLUMN_WIDTH) newest_stm_vers = String('Downloading Newest Firmware info...') piksi_nap_vers = String('Waiting for Piksi to send settings...') newest_nap_vers = String('Downloading Newest Firmware info...') local_console_vers = String('v' + CONSOLE_VERSION) newest_console_vers = String('Downloading Newest Console info...') erase_stm = Bool(True) erase_en = Bool(True) update_stm_firmware = Button(label='Update STM') update_nap_firmware = Button(label='Update NAP') update_full_firmware = Button(label='Update Piksi STM and NAP Firmware') updating = Bool(False) update_stm_en = Bool(False) update_nap_en = Bool(False) update_en = Bool(False) download_firmware = Button(label='Download Newest Firmware Files') download_stm = Button(label='Download', height=HT) download_nap = Button(label='Download', height=HT) downloading = Bool(False) download_fw_en = Bool(True) stm_fw = Instance(IntelHexFileDialog) nap_fw = Instance(IntelHexFileDialog) stream = Instance(OutputStream) view = View( VGroup( HGroup( VGroup( Item('piksi_stm_vers', label='Current', resizable=True), Item('newest_stm_vers', label='Latest', resizable=True), Item('stm_fw', style='custom', show_label=True, \ label="Local File", enabled_when='download_fw_en'), HGroup(Item('update_stm_firmware', show_label=False, \ enabled_when='update_stm_en'), Item('erase_stm', label='Erase STM flash\n(recommended)', \ enabled_when='erase_en', show_label=True)), show_border=True, label="STM Firmware Version" ), VGroup( Item('piksi_nap_vers', label='Current', resizable=True), Item('newest_nap_vers', label='Latest', resizable=True), Item('nap_fw', style='custom', show_label=True, \ label="Local File", enabled_when='download_fw_en'), HGroup(Item('update_nap_firmware', show_label=False, \ enabled_when='update_nap_en'), Item(width=50, label=" ")), show_border=True, label="NAP Firmware Version" ), VGroup( Item('local_console_vers', label='Current', resizable=True), Item('newest_console_vers', label='Latest'), label="Piksi Console Version", show_border=True), ), UItem('download_firmware', enabled_when='download_fw_en'), UItem('update_full_firmware', enabled_when='update_en'), Item( 'stream', style='custom', editor=InstanceEditor(), show_label=False, ), ) ) def __init__(self, link, prompt=True): """ Traits tab with UI for updating Piksi firmware. Parameters ---------- link : sbp.client.handler.Handler Link for SBP transfer to/from Piksi. prompt : bool Prompt user to update console/firmware if out of date. """ self.link = link self.settings = {} self.prompt = prompt self.python_console_cmds = {'update': self} self.update_dl = None self.erase_en = True self.stm_fw = IntelHexFileDialog('STM') self.stm_fw.on_trait_change(self._manage_enables, 'status') self.nap_fw = IntelHexFileDialog('M25') self.nap_fw.on_trait_change(self._manage_enables, 'status') self.stream = OutputStream() self.get_latest_version_info() def _manage_enables(self): """ Manages whether traits widgets are enabled in the UI or not. """ if self.updating == True or self.downloading == True: self.update_stm_en = False self.update_nap_en = False self.update_en = False self.download_fw_en = False self.erase_en = False else: self.download_fw_en = True self.erase_en = True if self.stm_fw.ihx is not None: self.update_stm_en = True else: self.update_stm_en = False self.update_en = False if self.nap_fw.ihx is not None: self.update_nap_en = True else: self.update_nap_en = False self.update_en = False if self.nap_fw.ihx is not None and self.stm_fw.ihx is not None: self.update_en = True def _updating_changed(self): """ Handles self.updating trait being changed. """ self._manage_enables() def _downloading_changed(self): """ Handles self.downloading trait being changed. """ self._manage_enables() def _write(self, text): """ Stream style write function. Allows flashing debugging messages to be routed to embedded text console. Parameters ---------- text : string Text to be written to screen. """ self.stream.write(text) self.stream.write('\n') self.stream.flush() def _update_stm_firmware_fired(self): """ Handle update_stm_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._firmware_update_thread.is_alive(): return except AttributeError: pass self._firmware_update_thread = Thread( target=self.manage_firmware_updates, args=("STM", )) self._firmware_update_thread.start() def _update_nap_firmware_fired(self): """ Handle update_nap_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._firmware_update_thread.is_alive(): return except AttributeError: pass self._firmware_update_thread = Thread( target=self.manage_firmware_updates, args=("M25", )) self._firmware_update_thread.start() def _update_full_firmware_fired(self): """ Handle update_full_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._firmware_update_thread.is_alive(): return except AttributeError: pass self._firmware_update_thread = Thread( target=self.manage_firmware_updates, args=("ALL", )) self._firmware_update_thread.start() def _download_firmware(self): """ Download latest firmware from swiftnav.com. """ self._write('') # Check that we received the index file from the website. if self.update_dl == None: self._write("Error: Can't download firmware files") return self.downloading = True status = 'Downloading Newest Firmware...' self.nap_fw.clear(status) self.stm_fw.clear(status) self._write(status) # Get firmware files from Swift Nav's website, save to disk, and load. try: self._write('Downloading Newest NAP firmware') filepath = self.update_dl.download_nap_firmware() self._write('Saved file to %s' % filepath) self.nap_fw.load_ihx(filepath) except AttributeError: self.nap_fw.clear("Error downloading firmware") self._write( "Error downloading firmware: index file not downloaded yet") except KeyError: self.nap_fw.clear("Error downloading firmware") self._write("Error downloading firmware: URL not present in index") except URLError: self.nap_fw.clear("Error downloading firmware") self._write( "Error: Failed to download latest NAP firmware from Swift Navigation's website" ) try: self._write('Downloading Newest STM firmware') filepath = self.update_dl.download_stm_firmware() self._write('Saved file to %s' % filepath) self.stm_fw.load_ihx(filepath) except AttributeError: self.stm_fw.clear("Error downloading firmware") self._write( "Error downloading firmware: index file not downloaded yet") except KeyError: self.stm_fw.clear("Error downloading firmware") self._write("Error downloading firmware: URL not present in index") except URLError: self.stm_fw.clear("Error downloading firmware") self._write( "Error: Failed to download latest STM firmware from Swift Navigation's website" ) self.downloading = False def _download_firmware_fired(self): """ Handle download_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._download_firmware_thread.is_alive(): return except AttributeError: pass self._download_firmware_thread = Thread(target=self._download_firmware) self._download_firmware_thread.start() def compare_versions(self): """ To be called after latest Piksi firmware info has been received from device, to decide if current firmware on Piksi is out of date. Starts a thread so as not to block GUI thread. """ try: if self._compare_versions_thread.is_alive(): return except AttributeError: pass self._compare_versions_thread = Thread(target=self._compare_versions) self._compare_versions_thread.start() def _compare_versions(self): """ Compares version info between received firmware version / current console and firmware / console info from website to decide if current firmware or console is out of date. Prompt user to update if so. """ # Check that settings received from Piksi contain FW versions. try: self.piksi_stm_vers = \ self.settings['system_info']['firmware_version'].value self.piksi_nap_vers = \ self.settings['system_info']['nap_version'].value except KeyError: self._write( "\nError: Settings received from Piksi don't contain firmware version keys. Please contact Swift Navigation.\n" ) return # Check that we received the index file from the website. if self.update_dl == None: self._write( "Error: No website index to use to compare versions with local firmware" ) return # Check if console is out of date and notify user if so. if self.prompt: local_console_version = parse_version(CONSOLE_VERSION) remote_console_version = parse_version(self.newest_console_vers) self.console_outdated = remote_console_version > local_console_version if self.console_outdated: console_outdated_prompt = \ prompt.CallbackPrompt( title="Piksi Console Outdated", actions=[prompt.close_button], ) console_outdated_prompt.text = \ "Your Piksi Console is out of date and may be incompatible\n" + \ "with current firmware. We highly recommend upgrading to\n" + \ "ensure proper behavior.\n\n" + \ "Please visit http://downloads.swiftnav.com to\n" + \ "download the newest version.\n\n" + \ "Local Console Version :\n\t" + \ "v" + CONSOLE_VERSION + \ "\nNewest Console Version :\n\t" + \ self.update_dl.index['piksi_v2.3.1']['console']['version'] + "\n" console_outdated_prompt.run() # For timing aesthetics between windows popping up. sleep(0.5) # Check if firmware is out of date and notify user if so. if self.prompt: local_stm_version = parse_version( self.settings['system_info']['firmware_version'].value) remote_stm_version = parse_version(self.newest_stm_vers) local_nap_version = parse_version( self.settings['system_info']['nap_version'].value) remote_nap_version = parse_version(self.newest_nap_vers) self.fw_outdated = remote_nap_version > local_nap_version or \ remote_stm_version > local_stm_version if self.fw_outdated: fw_update_prompt = \ prompt.CallbackPrompt( title='Firmware Update', actions=[prompt.close_button] ) fw_update_prompt.text = \ "New Piksi firmware available.\n\n" + \ "Please use the Firmware Update tab to update.\n\n" + \ "Newest STM Version :\n\t%s\n\n" % \ self.update_dl.index['piksi_v2.3.1']['stm_fw']['version'] + \ "Newest SwiftNAP Version :\n\t%s\n\n" % \ self.update_dl.index['piksi_v2.3.1']['nap_fw']['version'] fw_update_prompt.run() def get_latest_version_info(self): """ Get latest firmware / console version from website. Starts thread so as not to block the GUI thread. """ try: if self._get_latest_version_info_thread.is_alive(): return except AttributeError: pass self._get_latest_version_info_thread = Thread( target=self._get_latest_version_info) self._get_latest_version_info_thread.start() def _get_latest_version_info(self): """ Get latest firmware / console version from website. """ try: self.update_dl = UpdateDownloader() except URLError: self._write( "\nError: Failed to download latest file index from Swift Navigation's website. Please visit our website to check that you're running the latest Piksi firmware and Piksi console.\n" ) return # Make sure index contains all keys we are interested in. try: self.newest_stm_vers = self.update_dl.index['piksi_v2.3.1'][ 'stm_fw']['version'] self.newest_nap_vers = self.update_dl.index['piksi_v2.3.1'][ 'nap_fw']['version'] self.newest_console_vers = self.update_dl.index['piksi_v2.3.1'][ 'console']['version'] except KeyError: self._write( "\nError: Index downloaded from Swift Navigation's website (%s) doesn't contain all keys. Please contact Swift Navigation.\n" % INDEX_URL) return def manage_stm_firmware_update(self): # Erase all of STM's flash (other than bootloader) if box is checked. if self.erase_stm: text = "Erasing STM" self._write(text) self.create_flash("STM") sectors_to_erase = set(range(self.pk_flash.n_sectors)).difference( set(self.pk_flash.restricted_sectors)) progress_dialog = PulsableProgressDialog(len(sectors_to_erase), False) progress_dialog.title = text GUI.invoke_later(progress_dialog.open) erase_count = 0 for s in sorted(sectors_to_erase): progress_dialog.progress(erase_count) self._write('Erasing %s sector %d' % (self.pk_flash.flash_type, s)) self.pk_flash.erase_sector(s) erase_count += 1 self.stop_flash() self._write("") progress_dialog.close() # Flash STM. text = "Updating STM" self._write(text) self.create_flash("STM") stm_n_ops = self.pk_flash.ihx_n_ops(self.stm_fw.ihx, \ erase = not self.erase_stm) progress_dialog = PulsableProgressDialog(stm_n_ops, True) progress_dialog.title = text GUI.invoke_later(progress_dialog.open) # Don't erase sectors if we've already done so above. self.pk_flash.write_ihx(self.stm_fw.ihx, self.stream, mod_print=0x40, \ elapsed_ops_cb = progress_dialog.progress, \ erase = not self.erase_stm) self.stop_flash() self._write("") progress_dialog.close() def manage_nap_firmware_update(self, check_version=False): # Flash NAP if out of date. try: local_nap_version = parse_version( self.settings['system_info']['nap_version'].value) remote_nap_version = parse_version(self.newest_nap_vers) nap_out_of_date = local_nap_version != remote_nap_version except KeyError: nap_out_of_date = True if nap_out_of_date or check_version == False: text = "Updating NAP" self._write(text) self.create_flash("M25") nap_n_ops = self.pk_flash.ihx_n_ops(self.nap_fw.ihx) progress_dialog = PulsableProgressDialog(nap_n_ops, True) progress_dialog.title = text GUI.invoke_later(progress_dialog.open) self.pk_flash.write_ihx(self.nap_fw.ihx, self.stream, mod_print=0x40, \ elapsed_ops_cb = progress_dialog.progress) self.stop_flash() self._write("") progress_dialog.close() return True else: text = "NAP is already to latest version, not updating!" self._write(text) self._write("") return False # Executed in GUI thread, called from Handler. def manage_firmware_updates(self, device): """ Update Piksi firmware. Erase entire STM flash (other than bootloader) if so directed. Flash NAP only if new firmware is available. """ self.updating = True update_nap = False self._write('') if device == "STM": self.manage_stm_firmware_update() elif device == "M25": update_nap = self.manage_nap_firmware_update() else: self.manage_stm_firmware_update() update_nap = self.manage_nap_firmware_update(check_version=True) # Must tell Piksi to jump to application after updating firmware. if device == "STM" or update_nap: self.link(MsgBootloaderJumpToApp(jump=0)) self._write("Firmware update finished.") self._write("") self.updating = False def create_flash(self, flash_type): """ Create flash.Flash instance and set Piksi into bootloader mode, prompting user to reset if necessary. Parameter --------- flash_type : string Either "STM" or "M25". """ # Reset device if the application is running to put into bootloader mode. self.link(MsgReset()) self.pk_boot = bootload.Bootloader(self.link) self._write("Waiting for bootloader handshake message from Piksi ...") reset_prompt = None handshake_received = self.pk_boot.handshake(1) # Prompt user to reset Piksi if we don't receive the handshake message # within a reasonable amount of tiime (firmware might be corrupted). while not handshake_received: reset_prompt = \ prompt.CallbackPrompt( title="Please Reset Piksi", actions=[prompt.close_button], ) reset_prompt.text = \ "You must press the reset button on your Piksi in order\n" + \ "to update your firmware.\n\n" + \ "Please press it now.\n\n" reset_prompt.run(block=False) while not reset_prompt.closed and not handshake_received: handshake_received = self.pk_boot.handshake(1) reset_prompt.kill() reset_prompt.wait() self._write("received bootloader handshake message.") self._write("Piksi Onboard Bootloader Version: " + self.pk_boot.version) self.pk_flash = flash.Flash(self.link, flash_type, self.pk_boot.sbp_version) def stop_flash(self): """ Stop Flash and Bootloader instances (removes callback from SerialLink). """ self.pk_flash.stop() self.pk_boot.stop()
def traits_view(self): tgrp = HGroup(icon_button_editor('editor.stop_button', 'stop', tooltip='Stop the current scan')) v = View(VGroup(tgrp, UItem('graph', style='custom', editor=InstanceEditor()))) return v
class UpdateView(HasTraits): piksi_hw_rev = String('piksi_multi') is_v2 = Bool(False) piksi_stm_vers = String('Waiting for Piksi to send settings...', width=COLUMN_WIDTH) newest_stm_vers = String('Downloading Latest Firmware info...') piksi_nap_vers = String('Waiting for Piksi to send settings...') newest_nap_vers = String('Downloading Latest Firmware info...') local_console_vers = String('v' + CONSOLE_VERSION) newest_console_vers = String('Downloading Latest Console info...') erase_stm = Bool(True) erase_en = Bool(True) update_stm_firmware = Button(label='Update FW') update_nap_firmware = Button(label='Update NAP') update_full_firmware = Button(label='Update Piksi STM and NAP Firmware') updating = Bool(False) update_stm_en = Bool(False) update_nap_en = Bool(False) update_en = Bool(False) serial_upgrade = Bool(False) upgrade_steps = String("Firmware upgrade steps:") download_firmware = Button(label='Download Latest Firmware') download_directory = Directory( " Please choose a directory for downloaded firmware files...") download_stm = Button(label='Download', height=HT) download_nap = Button(label='Download', height=HT) downloading = Bool(False) download_fw_en = Bool(True) stm_fw = Instance(FirmwareFileDialog) nap_fw = Instance(FirmwareFileDialog) stream = Instance(OutputStream) view = View( VGroup( Item('piksi_hw_rev', label='Hardware Revision', editor_args={'enabled': False}, resizable=True), HGroup( VGroup( Item('piksi_stm_vers', label='Current', resizable=True, editor_args={'enabled': False}), Item('newest_stm_vers', label='Latest', resizable=True, editor_args={'enabled': False, 'readonly_allow_selection': True}), Item('stm_fw', style='custom', show_label=True, \ label="Local File", enabled_when='download_fw_en', visible_when='serial_upgrade', editor_args={'enabled': False}), HGroup(Item('update_stm_firmware', show_label=False, \ enabled_when='update_stm_en', visible_when='serial_upgrade'), Item('erase_stm', label='Erase STM flash\n(recommended)', \ enabled_when='erase_en', show_label=True, visible_when='is_v2')), show_border=True, label="Firmware Version" ), VGroup( Item('piksi_nap_vers', label='Current', resizable=True, editor_args={'enabled': False}), Item('newest_nap_vers', label='Latest', resizable=True, editor_args={'enabled': False}), Item('nap_fw', style='custom', show_label=True, \ label="Local File", enabled_when='download_fw_en', editor_args={'enabled': False}), HGroup(Item('update_nap_firmware', show_label=False, \ enabled_when='update_nap_en', visible_when='serial_upgrade'), Item(width=50, label=" ")), show_border=True, label="NAP Version", visible_when='is_v2' ), VGroup( Item('local_console_vers', label='Current', resizable=True, editor_args={'enabled': False}), Item('newest_console_vers', label='Latest', editor_args={'enabled': False}), label="Swift Console Version", show_border=True), ), UItem('download_directory', enabled_when='download_fw_en'), UItem('download_firmware', enabled_when='download_fw_en'), UItem('update_full_firmware', enabled_when='update_en', visible_when='is_v2'), VGroup( UItem('upgrade_steps', visible_when='not serial_upgrade', style='readonly'), Item( 'stream', style='custom', editor=InstanceEditor(), show_label=False, ), show_border=True, ) ) ) def __init__(self, link, prompt=True, serial_upgrade=False): """ Traits tab with UI for updating Piksi firmware. Parameters ---------- link : sbp.client.handler.Handler Link for SBP transfer to/from Piksi. prompt : bool Prompt user to update console/firmware if out of date. """ self.link = link self.settings = {} self.prompt = prompt self.python_console_cmds = {'update': self} try: self.update_dl = UpdateDownloader() except URLError: self.update_dl = None pass self.erase_en = True self.stm_fw = FirmwareFileDialog('bin') self.stm_fw.on_trait_change(self._manage_enables, 'status') self.nap_fw = FirmwareFileDialog('M25') self.nap_fw.on_trait_change(self._manage_enables, 'status') self.stream = OutputStream() self.serial_upgrade = serial_upgrade self.last_call_fw_version = None if not self.serial_upgrade: self._write( "1. Insert the USB flash drive provided with your Piki Multi into " "your computer. Select the flash drive root directory as the " "firmware download destination using the \"Please " "choose a directory for downloaded firmware files\" directory " "chooser above. Press the \"Download Latest Firmware\" button. " "This will download the latest Piksi Multi firmware file onto the " "USB flashdrive.\n" "2. Eject the drive from your computer and plug it " "into the Piksi Multi evaluation board.\n" "3. Reset your Piksi Multi and it will upgrade to the version " "on the USB flash drive. This should take less than 5 minutes.\n" "4. When the upgrade completes you will be prompted to remove the " "USB flash drive and reset your Piksi Multi.\n" "5. Verify that the firmware version has upgraded via inspection " "of the Current Firmware Version box on the Firmware Update Tab " "of the Swift Console.\n") def _manage_enables(self): """ Manages whether traits widgets are enabled in the UI or not. """ if self.updating == True or self.downloading == True: self.update_stm_en = False self.update_nap_en = False self.update_en = False self.download_fw_en = False self.erase_en = False else: self.download_fw_en = True self.erase_en = True if self.stm_fw.ihx is not None or self.stm_fw.blob is not None: self.update_stm_en = True else: self.update_stm_en = False self.update_en = False if self.nap_fw.ihx is not None: self.update_nap_en = True else: self.update_nap_en = False self.update_en = False if self.nap_fw.ihx is not None and self.stm_fw.ihx is not None: self.update_en = True def _download_directory_changed(self): if self.update_dl: self.update_dl.set_root_path(self.download_directory) def _updating_changed(self): """ Handles self.updating trait being changed. """ self._manage_enables() def _downloading_changed(self): """ Handles self.downloading trait being changed. """ self._manage_enables() def _write(self, text): """ Stream style write function. Allows flashing debugging messages to be routed to embedded text console. Parameters ---------- text : string Text to be written to screen. """ self.stream.write(text) self.stream.write('\n') self.stream.flush() def _update_stm_firmware_fired(self): """ Handle update_stm_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._firmware_update_thread.is_alive(): return except AttributeError: pass self._firmware_update_thread = Thread( target=self.manage_firmware_updates, args=("STM", )) self._firmware_update_thread.start() def _update_nap_firmware_fired(self): """ Handle update_nap_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._firmware_update_thread.is_alive(): return except AttributeError: pass self._firmware_update_thread = Thread( target=self.manage_firmware_updates, args=("M25", )) self._firmware_update_thread.start() def _update_full_firmware_fired(self): """ Handle update_full_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._firmware_update_thread.is_alive(): return except AttributeError: pass self._firmware_update_thread = Thread( target=self.manage_firmware_updates, args=("ALL", )) self._firmware_update_thread.start() def _download_firmware(self): """ Download latest firmware from swiftnav.com. """ self._write('') # Check that we received the index file from the website. if self.update_dl == None: self._write("Error: Can't download firmware files") return self.downloading = True status = 'Downloading Latest Firmware...' self.nap_fw.clear(status) self.stm_fw.clear(status) self._write(status) # Get firmware files from Swift Nav's website, save to disk, and load. if self.update_dl.index[self.piksi_hw_rev].has_key('fw'): try: self._write('Downloading Latest Multi firmware') filepath = self.update_dl.download_multi_firmware( self.piksi_hw_rev) self._write('Saved file to %s' % filepath) self.stm_fw.load_bin(filepath) except AttributeError: self.nap_fw.clear("Error downloading firmware") self._write( "Error downloading firmware: index file not downloaded yet" ) except KeyError: self.nap_fw.clear("Error downloading firmware") self._write( "Error downloading firmware: URL not present in index") except URLError: self.nap_fw.clear("Error downloading firmware") self._write( "Error: Failed to download latest NAP firmware from Swift Navigation's website" ) self.downloading = False return try: self._write('Downloading Latest NAP firmware') filepath = self.update_dl.download_nap_firmware(self.piksi_hw_rev) self._write('Saved file to %s' % filepath) self.nap_fw.load_ihx(filepath) except AttributeError: self.nap_fw.clear("Error downloading firmware") self._write( "Error downloading firmware: index file not downloaded yet") except KeyError: self.nap_fw.clear("Error downloading firmware") self._write("Error downloading firmware: URL not present in index") except URLError: self.nap_fw.clear("Error downloading firmware") self._write( "Error: Failed to download latest NAP firmware from Swift Navigation's website" ) try: self._write('Downloading Latest STM firmware') filepath = self.update_dl.download_stm_firmware(self.piksi_hw_rev) self._write('Saved file to %s' % filepath) self.stm_fw.load_ihx(filepath) except AttributeError: self.stm_fw.clear("Error downloading firmware") self._write( "Error downloading firmware: index file not downloaded yet") except KeyError: self.stm_fw.clear("Error downloading firmware") self._write("Error downloading firmware: URL not present in index") except URLError: self.stm_fw.clear("Error downloading firmware") self._write( "Error: Failed to download latest STM firmware from Swift Navigation's website" ) self.downloading = False def _download_firmware_fired(self): """ Handle download_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._download_firmware_thread.is_alive(): return except AttributeError: pass self._download_firmware_thread = Thread(target=self._download_firmware) self._download_firmware_thread.start() def compare_versions(self): """ To be called after latest Piksi firmware info has been received from device, to decide if current firmware on Piksi is out of date. Also informs user if the firmware was successfully upgraded. Starts a thread so as not to block GUI thread. """ try: if self._compare_versions_thread.is_alive(): return except AttributeError: pass self._compare_versions_thread = Thread(target=self._compare_versions) self._compare_versions_thread.start() def _compare_versions(self): """ Compares version info between received firmware version / current console and firmware / console info from website to decide if current firmware or console is out of date. Prompt user to update if so. Informs user if firmware successfully upgraded. """ # Check that settings received from Piksi contain FW versions. try: self.piksi_hw_rev = \ HW_REV_LOOKUP[self.settings['system_info']['hw_revision'].value] self.piksi_stm_vers = \ self.settings['system_info']['firmware_version'].value except KeyError: self._write( "\nError: Settings received from Piksi don't contain firmware version keys. Please contact Swift Navigation.\n" ) return self.is_v2 = self.piksi_hw_rev.startswith('piksi_v2') if self.is_v2: self.stm_fw.set_flash_type('STM') self.serial_upgrade = True else: self.stm_fw.set_flash_type('bin') self._get_latest_version_info() # Check that we received the index file from the website. if self.update_dl == None: self._write( "Error: No website index to use to compare versions with local firmware" ) return # Check if console is out of date and notify user if so. if self.prompt: local_console_version = parse_version(CONSOLE_VERSION) remote_console_version = parse_version(self.newest_console_vers) self.console_outdated = remote_console_version > local_console_version # we want to warn users using v2 regardless of version logic if self.console_outdated or self.is_v2: if not self.is_v2: console_outdated_prompt = \ prompt.CallbackPrompt( title="Swift Console Outdated", actions=[prompt.close_button], ) console_outdated_prompt.text = \ "Your console is out of date and may be incompatible\n" + \ "with current firmware. We highly recommend upgrading to\n" + \ "ensure proper behavior.\n\n" + \ "Please visit http://support.swiftnav.com to\n" + \ "download the latest version.\n\n" + \ "Local Console Version :\n\t" + \ "v" + CONSOLE_VERSION + \ "\nLatest Console Version :\n\t" + \ self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n" else: console_outdated_prompt = \ prompt.CallbackPrompt( title="Swift Console Incompatible", actions=[prompt.close_button], ) console_outdated_prompt.text = \ "Your console is incompatible with your hardware revision.\n" + \ "We highly recommend using a compatible console version\n" + \ "to ensure proper behavior.\n\n" + \ "Please visit http://support.swiftnav.com to\n" + \ "download the latest compatible version.\n\n" + \ "Current Hardware revision :\n\t" + \ self.piksi_hw_rev + \ "\nLast supported Console Version: \n\t" + \ self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n" console_outdated_prompt.run() # For timing aesthetics between windows popping up. sleep(0.5) # Check if firmware is out of date and notify user if so. local_stm_version = self.settings['system_info'][ 'firmware_version'].value remote_stm_version = self.newest_stm_vers self.fw_outdated = remote_stm_version > local_stm_version if self.fw_outdated: fw_update_prompt = \ prompt.CallbackPrompt( title='Firmware Update', actions=[prompt.close_button] ) if self.update_dl.index[self.piksi_hw_rev].has_key('fw'): fw_update_prompt.text = \ "New Piksi firmware available.\n\n" + \ "Please use the Firmware Update tab to update.\n\n" + \ "Newest Firmware Version :\n\t%s\n\n" % \ self.update_dl.index[self.piksi_hw_rev]['fw']['version'] else: fw_update_prompt.text = \ "New Piksi firmware available.\n\n" + \ "Please use the Firmware Update tab to update.\n\n" + \ "Newest STM Version :\n\t%s\n\n" % \ self.update_dl.index[self.piksi_hw_rev]['stm_fw']['version'] + \ "Newest SwiftNAP Version :\n\t%s\n\n" % \ self.update_dl.index[self.piksi_hw_rev]['nap_fw']['version'] fw_update_prompt.run() # Check if firmware successfully upgraded and notify user if so. if self.last_call_fw_version is not None and \ self.last_call_fw_version != local_stm_version: fw_success_str = "Firmware successfully upgraded from %s to %s." % \ (self.last_call_fw_version, local_stm_version) print fw_success_str self._write(fw_success_str) # Record firmware version reported each time this callback is called. self.last_call_fw_version = local_stm_version def _get_latest_version_info(self): """ Get latest firmware / console version from website. """ try: self.update_dl = UpdateDownloader() except URLError: self._write( "\nError: Failed to download latest file index from Swift Navigation's website. Please visit our website to check that you're running the latest Piksi firmware and Piksi console.\n" ) return # Make sure index contains all keys we are interested in. try: if self.update_dl.index[self.piksi_hw_rev].has_key('fw'): self.newest_stm_vers = self.update_dl.index[ self.piksi_hw_rev]['fw']['version'] else: self.newest_stm_vers = self.update_dl.index[ self.piksi_hw_rev]['stm_fw']['version'] self.newest_nap_vers = self.update_dl.index[ self.piksi_hw_rev]['nap_fw']['version'] self.newest_console_vers = self.update_dl.index[ self.piksi_hw_rev]['console']['version'] except KeyError: self._write( "\nError: Index downloaded from Swift Navigation's website (%s) doesn't contain all keys. Please contact Swift Navigation.\n" % INDEX_URL) return def manage_stm_firmware_update(self): # Erase all of STM's flash (other than bootloader) if box is checked. if self.erase_stm: text = "Erasing STM" self._write(text) self.create_flash("STM") sectors_to_erase = set(range(self.pk_flash.n_sectors)).difference( set(self.pk_flash.restricted_sectors)) progress_dialog = PulsableProgressDialog(len(sectors_to_erase), False) progress_dialog.title = text GUI.invoke_later(progress_dialog.open) erase_count = 0 for s in sorted(sectors_to_erase): progress_dialog.progress(erase_count) self._write('Erasing %s sector %d' % (self.pk_flash.flash_type, s)) self.pk_flash.erase_sector(s) erase_count += 1 self.stop_flash() self._write("") try: progress_dialog.close() except AttributeError: pass # Flash STM. text = "Updating STM" self._write(text) self.create_flash("STM") stm_n_ops = self.pk_flash.ihx_n_ops(self.stm_fw.ihx, \ erase = not self.erase_stm) progress_dialog = PulsableProgressDialog(stm_n_ops, True) progress_dialog.title = text GUI.invoke_later(progress_dialog.open) # Don't erase sectors if we've already done so above. self.pk_flash.write_ihx(self.stm_fw.ihx, self.stream, mod_print=0x40, \ elapsed_ops_cb = progress_dialog.progress, \ erase = not self.erase_stm) self.stop_flash() self._write("") try: progress_dialog.close() except AttributeError: pass def manage_nap_firmware_update(self, check_version=False): # Flash NAP if out of date. try: local_nap_version = parse_version( self.settings['system_info']['nap_version'].value) remote_nap_version = parse_version(self.newest_nap_vers) nap_out_of_date = local_nap_version != remote_nap_version except KeyError: nap_out_of_date = True if nap_out_of_date or check_version == False: text = "Updating NAP" self._write(text) self.create_flash("M25") nap_n_ops = self.pk_flash.ihx_n_ops(self.nap_fw.ihx) progress_dialog = PulsableProgressDialog(nap_n_ops, True) progress_dialog.title = text GUI.invoke_later(progress_dialog.open) self.pk_flash.write_ihx(self.nap_fw.ihx, self.stream, mod_print=0x40, \ elapsed_ops_cb = progress_dialog.progress) self.stop_flash() self._write("") try: progress_dialog.close() except AttributeError: pass return True else: text = "NAP is already to latest version, not updating!" self._write(text) self._write("") return False def manage_multi_firmware_update(self): # Set up progress dialog and transfer file to Piksi using SBP FileIO progress_dialog = PulsableProgressDialog(len(self.stm_fw.blob)) progress_dialog.title = "Transferring image file" GUI.invoke_later(progress_dialog.open) self._write("Transferring image file...") try: FileIO(self.link).write("upgrade.image_set.bin", self.stm_fw.blob, progress_cb=progress_dialog.progress) except Exception as e: self._write("Failed to transfer image file to Piksi: %s\n" % e) progress_dialog.close() return try: progress_dialog.close() except AttributeError: pass # Setup up pulsed progress dialog and commit to flash progress_dialog = PulsableProgressDialog(100, True) progress_dialog.title = "Committing to flash" GUI.invoke_later(progress_dialog.open) self._write("Committing file to flash...") def log_cb(msg, **kwargs): self._write(msg.text) self.link.add_callback(log_cb, SBP_MSG_LOG) code = shell_command(self.link, "upgrade_tool upgrade.image_set.bin", 600, progress_cb=progress_dialog.progress) self.link.remove_callback(log_cb, SBP_MSG_LOG) progress_dialog.close() if code != 0: self._write('Failed to perform upgrade (code = %d)' % code) if code == -255: self._write('Shell command timed out. Please try again.') return self._write('Resetting Piksi...') self.link(MsgReset(flags=0)) # Executed in GUI thread, called from Handler. def manage_firmware_updates(self, device): """ Update Piksi firmware. Erase entire STM flash (other than bootloader) if so directed. Flash NAP only if new firmware is available. """ self.updating = True update_nap = False self._write('') if not self.is_v2: self.manage_multi_firmware_update() self.updating = False return elif device == "STM": self.manage_stm_firmware_update() elif device == "M25": update_nap = self.manage_nap_firmware_update() else: self.manage_stm_firmware_update() update_nap = self.manage_nap_firmware_update(check_version=True) # Must tell Piksi to jump to application after updating firmware. if device == "STM" or update_nap: self.link(MsgBootloaderJumpToApp(jump=0)) self._write("Firmware update finished.") self._write("") self.updating = False def create_flash(self, flash_type): """ Create flash.Flash instance and set Piksi into bootloader mode, prompting user to reset if necessary. Parameter --------- flash_type : string Either "STM" or "M25". """ # Reset device if the application is running to put into bootloader mode. self.link(MsgReset(flags=0)) self.pk_boot = bootload.Bootloader(self.link) self._write("Waiting for bootloader handshake message from Piksi ...") reset_prompt = None handshake_received = self.pk_boot.handshake(1) # Prompt user to reset Piksi if we don't receive the handshake message # within a reasonable amount of tiime (firmware might be corrupted). while not handshake_received: reset_prompt = \ prompt.CallbackPrompt( title="Please Reset Piksi", actions=[prompt.close_button], ) reset_prompt.text = \ "You must press the reset button on your Piksi in order\n" + \ "to update your firmware.\n\n" + \ "Please press it now.\n\n" reset_prompt.run(block=False) while not reset_prompt.closed and not handshake_received: handshake_received = self.pk_boot.handshake(1) reset_prompt.kill() reset_prompt.wait() self._write("received bootloader handshake message.") self._write("Piksi Onboard Bootloader Version: " + self.pk_boot.version) self.pk_flash = flash.Flash(self.link, flash_type, self.pk_boot.sbp_version) def stop_flash(self): """ Stop Flash and Bootloader instances (removes callback from SerialLink). """ self.pk_flash.stop() self.pk_boot.stop()
class SwiftConsole(HasTraits): link = Instance(serial_link.SerialLink) console_output = Instance(OutputStream) python_console_env = Dict a = Int b = Int tracking_view = Instance(TrackingView) almanac_view = Instance(AlmanacView) solution_view = Instance(SolutionView) baseline_view = Instance(BaselineView) observation_view = Instance(ObservationView) observation_view_base = Instance(ObservationView) system_monitor_view = Instance(SystemMonitorView) simulator_view = Instance(SimulatorView) settings_view = Instance(SettingsView) view = View(VSplit( Tabbed(Item('tracking_view', style='custom', label='Tracking'), Item('almanac_view', style='custom', label='Almanac'), Item('solution_view', style='custom', label='Solution'), Item('baseline_view', style='custom', label='Baseline'), VSplit( Item('observation_view', style='custom', show_label=False), Item('observation_view_base', style='custom', show_label=False), label='Observations', ), Item('simulator_view', style='custom', label='Simulator'), Item('settings_view', style='custom', label='Settings'), Item('system_monitor_view', style='custom', label='System Monitor'), Item('python_console_env', style='custom', label='Python Console', editor=ShellEditor()), show_labels=False), Item( 'console_output', style='custom', editor=InstanceEditor(), height=0.3, show_label=False, ), ), icon=icon, resizable=True, width=800, height=600, title='Piksi Console, Version: ' + CONSOLE_VERSION) def print_message_callback(self, data): try: self.console_output.write(data.encode('ascii', 'ignore')) except UnicodeDecodeError: print "Oh crap!" def debug_var_callback(self, data): x = struct.unpack('<d', data[:8])[0] name = data[8:] print "VAR: %s = %d" % (name, x) def __init__(self, *args, **kwargs): try: update = kwargs.pop('update') except KeyError: update = True self.console_output = OutputStream() sys.stdout = self.console_output sys.stderr = self.console_output try: self.link = serial_link.SerialLink(*args, **kwargs) self.link.add_callback(ids.PRINT, self.print_message_callback) self.link.add_callback(ids.DEBUG_VAR, self.debug_var_callback) settings_read_finished_functions = [] self.tracking_view = TrackingView(self.link) self.almanac_view = AlmanacView(self.link) self.solution_view = SolutionView(self.link) self.baseline_view = BaselineView(self.link) self.observation_view = ObservationView(self.link, name='Rover', relay=False) self.observation_view_base = ObservationView(self.link, name='Base', relay=True) self.system_monitor_view = SystemMonitorView(self.link) self.simulator_view = SimulatorView(self.link) self.settings_view = SettingsView(self.link) if update: self.ocu = OneClickUpdate(self.link, self.console_output) settings_read_finished_functions.append(self.ocu.start) self.settings_view = \ SettingsView(self.link, settings_read_finished_functions) if update: self.ocu.point_to_settings(self.settings_view.settings) self.python_console_env = { 'send_message': self.link.send_message, 'link': self.link, } self.python_console_env.update( self.tracking_view.python_console_cmds) self.python_console_env.update( self.almanac_view.python_console_cmds) self.python_console_env.update( self.solution_view.python_console_cmds) self.python_console_env.update( self.baseline_view.python_console_cmds) self.python_console_env.update( self.observation_view.python_console_cmds) self.python_console_env.update( self.system_monitor_view.python_console_cmds) except: import traceback traceback.print_exc() def stop(self): self.link.close()
def traits_view(self): v = View( UItem('graph', style='custom', editor=InstanceEditor(view='panel_view'))) return v
class Mayavi(HasTraits): # The scene model. scene = Instance(MlabSceneModel, ()) # The mayavi engine view. engine_view = Instance(EngineView) # The current selection in the engine tree view. current_selection = Property ###################### view = View(HSplit( VSplit( Item(name='engine_view', style='custom', resizable=True, show_label=False), Item(name='current_selection', editor=InstanceEditor(), enabled_when='current_selection is not None', style='custom', springy=True, show_label=False), ), Item(name='scene', editor=SceneEditor(), show_label=False, resizable=True, height=500, width=500), ), resizable=True, scrollable=True) def __init__(self, **traits): HasTraits.__init__(self, **traits) self.engine_view = EngineView(engine=self.scene.engine) # Hook up the current_selection to change when the one in the engine # changes. This is probably unnecessary in Traits3 since you can show # the UI of a sub-object in T3. self.scene.engine.on_trait_change(self._selection_change, 'current_selection') self.generate_data_mayavi() def generate_data_mayavi(self): """Shows how you can generate data using mayavi instead of mlab.""" from mayavi.sources.api import ParametricSurface from mayavi.modules.api import Outline, Surface e = self.scene.engine s = ParametricSurface() e.add_source(s) e.add_module(Outline()) e.add_module(Surface()) def _selection_change(self, old, new): self.trait_property_changed('current_selection', old, new) def _get_current_selection(self): return self.scene.engine.current_selection
# ------------------------------------------------------------------------- # Define the View to use: # ------------------------------------------------------------------------- view = View( Group( [Item('company', editor=tree_editor, resizable=True), '|<>'], Group( [ '{Employee of the Month}@', Item( 'eom@', editor=InstanceEditor(values=[ InstanceDropChoice(klass=Employee, selectable=True) ]), resizable=True, ), '|<>', ], [ '{Department of the Month}@', Item( 'dom@', editor=InstanceEditor( values=[InstanceDropChoice(klass=Department)]), resizable=True, ), '|<>', ],
class Scene(TVTKScene, Widget): """A VTK interactor scene widget for pyface and PyQt. This widget uses a RenderWindowInteractor and therefore supports interaction with VTK widgets. The widget uses TVTK. In addition to the features that the base TVTKScene provides this widget supports: - saving the rendered scene to the clipboard. - picking data on screen. Press 'p' or 'P' when the mouse is over a point that you need to pick. - The widget also uses a light manager to manage the lighting of the scene. Press 'l' or 'L' to activate a GUI configuration dialog for the lights. - Pressing the left, right, up and down arrow let you rotate the camera in those directions. When shift-arrow is pressed then the camera is panned. Pressing the '+' (or '=') and '-' keys let you zoom in and out. - full screen rendering via the full_screen button on the UI. """ # The version of this class. Used for persistence. __version__ = 0 ########################################################################### # Traits. ########################################################################### # Turn on full-screen rendering. full_screen = Button('Full Screen') # The picker handles pick events. picker = Instance(picker.Picker) ######################################## # Render_window's view. _stereo_view = Group(Item(name='stereo_render'), Item(name='stereo_type'), show_border=True, label='Stereo rendering', ) # The default view of this object. default_view = View(Group( Group(Item(name='background'), Item(name='foreground'), Item(name='parallel_projection'), Item(name='disable_render'), Item(name='off_screen_rendering'), Item(name='jpeg_quality'), Item(name='jpeg_progressive'), Item(name='magnification'), Item(name='anti_aliasing_frames'), Item(name='full_screen', show_label=False), ), Group(Item(name='render_window', style='custom', visible_when='object.stereo', editor=InstanceEditor(view=View(_stereo_view)), show_label=False), ), label='Scene'), Group( Item(name='light_manager', style='custom', show_label=False), label='Lights'), Group( Item( name='movie_maker', style='custom', show_label=False ), label='Movie'), buttons=['OK', 'Cancel'] ) ######################################## # Private traits. _vtk_control = Instance(_VTKRenderWindowInteractor) _fullscreen = Any(False) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent=None, **traits): """ Initializes the object. """ # Base class constructor. super(Scene, self).__init__(parent, **traits) # Setup the default picker. self.picker = picker.Picker(self) # The light manager needs creating. self.light_manager = None self._cursor = QtCore.Qt.ArrowCursor def __get_pure_state__(self): """Allows us to pickle the scene.""" # The control attribute is not picklable since it is a VTK # object so we remove it. d = super(Scene, self).__get_pure_state__() for x in ['_vtk_control', '_fullscreen', '_cursor']: d.pop(x, None) return d ########################################################################### # 'Scene' interface. ########################################################################### def render(self): """ Force the scene to be rendered. Nothing is done if the `disable_render` trait is set to True.""" if not self.disable_render: self._vtk_control.Render() def get_size(self): """Return size of the render window.""" sz = self._vtk_control.size() return (sz.width(), sz.height()) def set_size(self, size): """Set the size of the window.""" self._vtk_control.resize(*size) def hide_cursor(self): """Hide the cursor.""" self._cursor = self._vtk_control.cursor().shape() self._vtk_control.setCursor(QtCore.Qt.BlankCursor) def show_cursor(self): """Show the cursor.""" self._vtk_control.setCursor(self._cursor) ########################################################################### # 'TVTKScene' interface. ########################################################################### def save_to_clipboard(self): """Saves a bitmap of the scene to the clipboard.""" handler, name = tempfile.mkstemp() self.save_bmp(name) QtGui.QApplication.clipboard().setImage(QtGui.QImage(name)) os.close(handler) os.unlink(name) ########################################################################### # 'event' interface. ########################################################################### def _closed_fired(self): super(Scene, self)._closed_fired() self.picker = None self._vtk_control = None self.control = None ########################################################################### # Non-public interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the widget. """ # Create the VTK widget. self._vtk_control = window = _VTKRenderWindowInteractor( self, parent, stereo=self.stereo) # Switch the default interaction style to the trackball one. window.GetInteractorStyle().SetCurrentStyleToTrackballCamera() # Grab the renderwindow. renwin = self._renwin = tvtk.to_tvtk(window.GetRenderWindow()) renwin.trait_set(point_smoothing=self.point_smoothing, line_smoothing=self.line_smoothing, polygon_smoothing=self.polygon_smoothing) # Create a renderer and add it to the renderwindow self._renderer = tvtk.Renderer() renwin.add_renderer(self._renderer) # Save a reference to our camera so it is not GC'd -- needed for # the sync_traits to work. self._camera = self.camera # Sync various traits. self._renderer.background = self.background self.sync_trait('background', self._renderer) self.renderer.on_trait_change(self.render, 'background') renwin.off_screen_rendering = self.off_screen_rendering self._camera.parallel_projection = self.parallel_projection self.sync_trait('parallel_projection', self._camera) self.sync_trait('off_screen_rendering', self._renwin) self.render_window.on_trait_change(self.render, 'off_screen_rendering') self.render_window.on_trait_change(self.render, 'stereo_render') self.render_window.on_trait_change(self.render, 'stereo_type') self.camera.on_trait_change(self.render, 'parallel_projection') self._interactor = tvtk.to_tvtk(window._Iren) return window def _lift(self): """Lift the window to the top. Useful when saving screen to an image.""" if self.render_window.off_screen_rendering: # Do nothing if off screen rendering is being used. return self._vtk_control.window().raise_() QtCore.QCoreApplication.processEvents() def _full_screen_fired(self): fs = self._fullscreen if fs: self._vtk_control.window().showNormal() self._fullscreen = False else: self._vtk_control.window().showFullScreen() self._fullscreen = True def _disable_fullscreen(self): fs = self._fullscreen if fs: self._vtk_control.window().showNormal() self._fullscreen = False def _busy_changed(self, val): GUI.set_busy(val)
from traitsui.api import (View, Group, Item, InstanceEditor, DropEditor, Tabbed) from tvtk.api import tvtk from tvtk.common import vtk_major_version VTK_VER = tvtk.Version().vtk_version # The properties view group. _prop_base_group = Group(Item(name='representation'), Item(name='color'), Item(name='line_width'), Item(name='point_size'), Item(name='opacity')) _prop_group = Group(Item(name='property', style='custom', show_label=False, editor=InstanceEditor(view=View(_prop_base_group))), Item(name='property', show_label=False, editor=InstanceEditor(label='More options ...')), show_border=True, label='Property') # The mapper's view group. if VTK_VER[:3] in ['4.2', '4.4']: _mapper_base_group = Group(Item(name='scalar_visibility')) else: _mapper_base_group = Group( Item(name='scalar_visibility'), Item(name='interpolate_scalars_before_mapping'), )
def traits_view(self): v = View(UItem('editor', style='custom', editor=InstanceEditor())) return v
class UpdateView(HasTraits): piksi_hw_rev = String('piksi_multi') is_v2 = Bool(False) piksi_stm_vers = String('Waiting for Piksi to send settings...', width=COLUMN_WIDTH) newest_stm_vers = String('Downloading Latest Firmware info...') piksi_nap_vers = String('Waiting for Piksi to send settings...') newest_nap_vers = String('Downloading Latest Firmware info...') local_console_vers = String('v' + CONSOLE_VERSION) newest_console_vers = String('Downloading Latest Console info...') download_directory_label = String('Firmware Download Directory:') update_stm_firmware = Button(label='Update Firmware') updating = Bool(False) update_stm_en = Bool(False) download_firmware = Button(label='Download Latest Firmware') download_directory = String() choose_dir = Button(label='...', padding=-1) download_stm = Button(label='Download', height=HT) downloading = Bool(False) download_fw_en = Bool(False) stm_fw = Instance(FirmwareFileDialog) stream = Instance(OutputStream) view = View( VGroup(Item('piksi_hw_rev', label='Hardware Revision', editor_args={'enabled': False}, resizable=True), HGroup( VGroup(Item('piksi_stm_vers', label='Current', resizable=True, editor_args={'enabled': False}), Item('newest_stm_vers', label='Latest', resizable=True, editor_args={ 'enabled': False, 'readonly_allow_selection': True }), Item('stm_fw', style='custom', show_label=True, label="Local File"), Item('update_stm_firmware', show_label=False, enabled_when='update_stm_en'), show_border=True, label="Firmware Version"), VGroup(Item('local_console_vers', label='Current', resizable=True, editor_args={'enabled': False}), Item('newest_console_vers', label='Latest', editor_args={'enabled': False}), label="Swift Console Version", show_border=True), ), HGroup( VGroup(HGroup( Item('download_directory', label="Directory", resizable=True), UItem('choose_dir', width=-0.1), ), HGroup( Spring(width=50, springy=False), Item('download_firmware', enabled_when='download_fw_en', show_label=False, resizable=True, springy=True)), label="Firmware Download", show_border=True), VGroup(Item( 'stream', style='custom', editor=InstanceEditor(), show_label=False, ), show_border=True, label="Firmware Upgrade Status"), ), show_border=True), ) def __init__(self, link, download_dir=None, prompt=True, connection_info={'mode': 'unknown'}): """ Traits tab with UI for updating Piksi firmware. Parameters ---------- link : sbp.client.handler.Handler Link for SBP transfer to/from Piksi. prompt : bool Prompt user to update console/firmware if out of date. """ self.link = link self.connection_info = connection_info self.settings = {} self.prompt = prompt self.python_console_cmds = {'update': self} self.download_directory = download_dir try: self.update_dl = UpdateDownloader(root_dir=self.download_directory) except RuntimeError: self.update_dl = None self.stm_fw = FirmwareFileDialog(self.download_directory) self.stm_fw.on_trait_change(self._manage_enables, 'status') self.stream = OutputStream() self.stream.max_len = 1000 self.last_call_fw_version = None self.link.add_callback(self.log_cb, SBP_MSG_LOG) def _choose_dir_fired(self): dialog = DirectoryDialog(label='Choose Download location', action='open', default_directory=self.download_directory) dialog.open() if dialog.return_code == OK: self.download_directory = dialog.path else: self._write('Error while selecting firmware download location') def _manage_enables(self): """ Manages whether traits widgets are enabled in the UI or not. """ if self.updating or self.downloading: self.update_stm_en = False self.download_fw_en = False else: if getattr(self.stm_fw, 'blob', None) is not None: self.update_stm_en = True else: self.update_stm_en = False if self.download_directory != '': self.download_fw_en = True def _download_directory_changed(self): if getattr(self, 'update_dl', None): self.update_dl.set_root_path(self.download_directory) self._manage_enables() def _updating_changed(self): """ Handles self.updating trait being changed. """ self._manage_enables() def _downloading_changed(self): """ Handles self.downloading trait being changed. """ self._manage_enables() def _clear_stream(self): self.stream.reset() def _write(self, text): """ Stream style write function. Allows flashing debugging messages to be routed to embedded text console. Parameters ---------- text : string Text to be written to screen. """ self.stream.write(text) self.stream.write('\n') self.stream.flush() def _update_stm_firmware_fired(self): """ Handle update_stm_firmware button. Starts thread so as not to block the GUI thread. """ if self.connection_info['mode'] != 'TCP/IP': self._write( "\n" "-----------------------------------------------\n" "USB Flashdrive Upgrade Procedure\n" "-----------------------------------------------\n" "\n" "1.\tInsert the USB flash drive provided with your Piksi Multi into your computer.\n" " \tSelect the flash drive root directory as the firmware download destination using the directory chooser above.\n" " \tPress the \"Download Latest Firmware\" button. This will download the latest Piksi Multi firmware file onto\n" " \tthe USB flashdrive.\n" "2.\tEject the drive from your computer and plug it into the USB Host port of the Piksi Multi evaluation board.\n" "3.\tReset your Piksi Multi and it will upgrade to the version on the USB flash drive.\n" " \tThis should take less than 5 minutes.\n" "4.\tWhen the upgrade completes you will be prompted to remove the USB flash drive and reset your Piksi Multi.\n" "5.\tVerify that the firmware version has upgraded via inspection of the Current Firmware Version box\n" " \ton the Update Tab of the Swift Console.\n") confirm_prompt = prompt.CallbackPrompt( title="Update device over serial connection?", actions=[ prompt.close_button, prompt.continue_via_serial_button ], callback=self._update_stm_firmware_fn) confirm_prompt.text = "\n" \ + " Upgrading your device via UART / RS232 may take up to 30 minutes. \n" \ + " \n" \ + " If the device you are upgrading has an accessible USB host port, it \n" \ " is recommended to instead follow the \'USB Flashdrive Upgrade \n" \ " Procedure\' that now appears in the Firmware upgrade status box. \n" \ + "\n" \ + " Are you sure you want to continue upgrading over serial?" confirm_prompt.run(block=False) else: self._update_stm_firmware_fn() def _replace_with_version_2(self): self.downloading = True self._write('Downloading Multi firmware v2.0.0') filepath = self.update_dl._download_file_from_url(V2_LINK) self._write('Saved file to %s' % filepath) self.stm_fw.load_bin(filepath) self.downloading = False def _update_stm_firmware_fn(self): try: if self._firmware_update_thread.is_alive(): return except AttributeError: pass current_fw_version = parse_version(self.piksi_stm_vers) re_result = re.search('[a-zA-Z0-9]*-(v[0-9]*\.[0-9]*\.[0-9])', self.stm_fw.status) intended_version = parse_version(re_result.group(1)) # If the current firmware is not yet beyond 2.0.0, and we are loading beyond 2.0.0 # warn the user that this upgrade is not possible if (current_fw_version < pkparse_version("v2.0.0") and intended_version > pkparse_version("v2.0.0")): confirm_prompt = prompt.CallbackPrompt( title="Update to v2.0.0", actions=[prompt.close_button, prompt.ok_button], callback=self._replace_with_version_2) confirm_prompt.text = "\n" \ + " Upgrading to firmware v2.1.0 or later requires that the device be \n" \ + " running firmware v2.0.0 or later. Please upgrade to firmware \n" \ + " version 2.0.0. \n" \ + " \n" \ + " Would you like to download firmware version v2.0.0 now? \n" \ + " \n" confirm_prompt.run(block=False) return self._firmware_update_thread = Thread( target=self.manage_firmware_updates, args=("STM", )) self._firmware_update_thread.start() def _download_firmware(self): """ Download latest firmware from swiftnav.com. """ self._write('') # Check that we received the index file from the website. if self.update_dl is None or self.update_dl.index is None: self._write("Error: Can't download firmware files") return self.downloading = True status = 'Downloading Latest Firmware...' self.stm_fw.clear(status) self._write(status) # Get firmware files from Swift Nav's website, save to disk, and load. if 'fw' in self.update_dl.index[self.piksi_hw_rev]: try: self._write('Downloading Latest Multi firmware') filepath = self.update_dl.download_multi_firmware( self.piksi_hw_rev) self._write('Saved file to %s' % filepath) self.stm_fw.load_bin(filepath) except AttributeError: self._write( "Error downloading firmware: index file not downloaded yet" ) except RuntimeError as e: self._write( "RunTimeError: unable to download firmware to path {0}: {1}" .format(self.download_directory, e)) except IOError as e: if e.errno == errno.EACCES or e.errno == errno.EPERM: self._write("IOError: unable to write to path %s. " "Verify that the path is writable." % self.download_directory) else: raise (e) except KeyError: self._write( "Error downloading firmware: URL not present in index") except URLError: self.nap_fw.clear("Error downloading firmware") self._write( "Error: Failed to download latest NAP firmware from Swift Navigation's website" ) self.downloading = False return def _download_firmware_fired(self): """ Handle download_firmware button. Starts thread so as not to block the GUI thread. """ try: if self._download_firmware_thread.is_alive(): return except AttributeError: pass self._download_firmware_thread = Thread(target=self._download_firmware) self._download_firmware_thread.start() def compare_versions(self): """ To be called after latest Piksi firmware info has been received from device, to decide if current firmware on Piksi is out of date. Also informs user if the firmware was successfully upgraded. Starts a thread so as not to block GUI thread. """ try: if self._compare_versions_thread.is_alive(): return except AttributeError: pass self._compare_versions_thread = Thread(target=self._compare_versions) self._compare_versions_thread.start() def _compare_versions(self): """ Compares version info between received firmware version / current console and firmware / console info from website to decide if current firmware or console is out of date. Prompt user to update if so. Informs user if firmware successfully upgraded. """ # Check that settings received from Piksi contain FW versions. try: self.piksi_hw_rev = HW_REV_LOOKUP[self.settings['system_info'] ['hw_revision'].value] self.piksi_stm_vers = self.settings['system_info'][ 'firmware_version'].value except KeyError: self._write( "\nError: Settings received from Piksi don't contain firmware version keys. Please contact Swift Navigation.\n" ) return self.is_v2 = self.piksi_hw_rev.startswith('piksi_v2') self._get_latest_version_info() # Check that we received the index file from the website. if self.update_dl is None: self._write( "Error: No website index to use to compare versions with local firmware" ) return # Get local stm version local_stm_version = None local_serial_number = None try: local_stm_version = self.settings['system_info'][ 'firmware_version'].value local_serial_number = self.settings['system_info'][ 'serial_number'].value except: # noqa pass # Check if console is out of date and notify user if so. if self.prompt: local_console_version = parse_version(CONSOLE_VERSION) remote_console_version = parse_version(self.newest_console_vers) self.console_outdated = remote_console_version > local_console_version # we want to warn users using v2 regardless of version logic if self.console_outdated or self.is_v2: if not self.is_v2: console_outdated_prompt = \ prompt.CallbackPrompt( title="Swift Console Outdated", actions=[prompt.close_button], ) console_outdated_prompt.text = \ "Your console is out of date and may be incompatible\n" + \ "with current firmware. We highly recommend upgrading to\n" + \ "ensure proper behavior.\n\n" + \ "Please visit http://support.swiftnav.com to\n" + \ "download the latest version.\n\n" + \ "Local Console Version :\n\t" + \ "v" + CONSOLE_VERSION + \ "\nLatest Console Version :\n\t" + \ self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n" else: console_outdated_prompt = \ prompt.CallbackPrompt( title="Swift Console Incompatible", actions=[prompt.close_button], ) console_outdated_prompt.text = \ "Your console is incompatible with your hardware revision.\n" + \ "We highly recommend using a compatible console version\n" + \ "to ensure proper behavior.\n\n" + \ "Please visit http://support.swiftnav.com to\n" + \ "download the latest compatible version.\n\n" + \ "Current Hardware revision :\n\t" + \ self.piksi_hw_rev + \ "\nLast supported Console Version: \n\t" + \ self.update_dl.index[self.piksi_hw_rev]['console']['version'] + "\n" console_outdated_prompt.run() # For timing aesthetics between windows popping up. sleep(0.5) # Check if firmware is out of date and notify user if so. remote_stm_version = self.newest_stm_vers self.fw_outdated = remote_stm_version > local_stm_version if local_stm_version.startswith('DEV'): self.fw_outdated = False if self.fw_outdated: fw_update_prompt = \ prompt.CallbackPrompt( title='Firmware Update', actions=[prompt.close_button] ) if 'fw' in self.update_dl.index[self.piksi_hw_rev]: fw_update_prompt.text = \ "New Piksi firmware available.\n\n" + \ "Please use the Update tab to update.\n\n" + \ "Newest Firmware Version :\n\t%s\n\n" % \ self.update_dl.index[self.piksi_hw_rev]['fw']['version'] else: fw_update_prompt.text = \ "New Piksi firmware available.\n\n" + \ "Please use the Update tab to update.\n\n" + \ "Newest STM Version :\n\t%s\n\n" % \ self.update_dl.index[self.piksi_hw_rev]['stm_fw']['version'] + \ "Newest SwiftNAP Version :\n\t%s\n\n" % \ self.update_dl.index[self.piksi_hw_rev]['nap_fw']['version'] fw_update_prompt.run() # Check if firmware successfully upgraded and notify user if so. if ((self.last_call_fw_version is not None and self.last_call_fw_version != local_stm_version) and (self.last_call_sn is None or local_serial_number is None or self.last_call_sn == local_serial_number)): fw_success_str = "Firmware successfully upgraded from %s to %s." % \ (self.last_call_fw_version, local_stm_version) print(fw_success_str) self._write(fw_success_str) # Record firmware version reported each time this callback is called. self.last_call_fw_version = local_stm_version self.last_call_sn = local_serial_number def _get_latest_version_info(self): """ Get latest firmware / console version from website. """ try: self.update_dl = UpdateDownloader(root_dir=self.download_directory) except RuntimeError: self._write( "\nError: Failed to download latest file index from Swift Navigation's website. Please visit our website to check that you're running the latest Piksi firmware and Piksi console.\n" ) self.update_dl = None return # Make sure index contains all keys we are interested in. try: if 'fw' in self.update_dl.index[self.piksi_hw_rev]: self.newest_stm_vers = self.update_dl.index[ self.piksi_hw_rev]['fw']['version'] else: self.newest_stm_vers = self.update_dl.index[ self.piksi_hw_rev]['stm_fw']['version'] self.newest_nap_vers = self.update_dl.index[ self.piksi_hw_rev]['nap_fw']['version'] self.newest_console_vers = self.update_dl.index[ self.piksi_hw_rev]['console']['version'] except KeyError: self._write( "\nError: Index downloaded from Swift Navigation's website (%s) doesn't contain all keys. Please contact Swift Navigation.\n" % INDEX_URL) return def file_transfer_progress_cb(self, arg): new_pcent = float(arg) / float(self.blob_size) * 100 if new_pcent - self.pcent_complete > 0.1: self.pcent_complete = new_pcent self.stream.scrollback_write( "{:2.1f} % of {:2.1f} MB transferred.".format( self.pcent_complete, self.blob_size * 1e-6)) def log_cb(self, msg, **kwargs): for regex in UPGRADE_WHITELIST: if re.match(regex, msg.text): self.stream.scrollback_write(msg.text.split("\n")[-1]) def manage_multi_firmware_update(self): self.blob_size = float(len(self.stm_fw.blob)) self.pcent_complete = 0 # Set up progress dialog and transfer file to Piksi using SBP FileIO self._clear_stream() self._write( "Transferring image to device...\n\n00.0 of {:2.1f} MB trasnferred" .format(self.blob_size * 1e-6)) try: FileIO(self.link).write("upgrade.image_set.bin", self.stm_fw.blob, progress_cb=self.file_transfer_progress_cb) except Exception as e: self._write("Failed to transfer image file to Piksi: %s\n" % e) self._write("Upgrade Aborted.") import traceback print(traceback.format_exc()) return -1 self.stream.scrollback_write( "Image transfer complete: {:2.1f} MB transferred.\n".format( self.blob_size * 1e-6)) # Setup up pulsed progress dialog and commit to flash self._write("Committing file to Flash...\n") self.link.add_callback(self.log_cb, SBP_MSG_LOG) code = shell_command(self.link, "upgrade_tool upgrade.image_set.bin", 200) self.link.remove_callback(self.log_cb, SBP_MSG_LOG) if code != 0: self._write('Failed to perform upgrade (code = %d)' % code) if code == -255: self._write('Shell command timed out. Please try again.') return self._write("Upgrade Complete.") self._write('Resetting Piksi...') self.link(MsgReset(flags=0)) # Executed in GUI thread, called from Handler. def manage_firmware_updates(self, device): """ Update Piksi firmware. Erase entire STM flash (other than bootloader) if so directed. Flash NAP only if new firmware is available. """ self.updating = True self._write('') if not self.is_v2: self.manage_multi_firmware_update() else: self._write( 'Unable to upgrade piksi v2; please use the last supported v2 console version.' ) self._write("") self.updating = False
class ExecutionLayerTemplate(BaseTemplate): # ------------------ # Regular Attributes # ------------------ #: Template referring to the primary surfactant data primary_surfactant_template = Instance(IngredientTemplate) #: Template referring to the secondary surfactant data secondary_surfactant_template = Instance(IngredientTemplate) #: A list of possible surfactant ingredient to choose from surfactant_template_list = List(IngredientTemplate) #: An empty surfactant Template empty_surfactant_template = Instance(IngredientTemplate) #: Template referring to the chloride ion data salt_template = Instance(IngredientTemplate) #: Template referring to the solvent data solvent_template = Instance(IngredientTemplate) #: Internal database of available Ingredient objects _gromacs_database = Instance(GromacsDatabase) # ------------------ # Properties # ------------------ #: Factory ID for Workflow id = Property(Unicode, depends_on='plugin_id') #: List containing all fragment templates to be used in #: a generated Workflow ingredient_templates = Property(List(IngredientTemplate), depends_on='primary_surfactant_template,' 'secondary_surfactant_template') #: Number of Ingredients n_ingredients = Property(Int, depends_on='ingredient_templates[]') # ------------------ # Properties # ------------------ #: A list of possible secondary surfactant ingredient, including an #: empty ingredient option secondary_surfactant_list = Property(List(IngredientTemplate), depends_on='surfactant_template_list,' 'primary_surfactant_template') # ------------------ # View # ------------------ traits_view = View( HGroup( Group(Heading("Primary Surfactant"), Item("primary_surfactant_template", editor=InstanceEditor(name='surfactant_template_list'), style='custom'), show_labels=False), Group(Heading("Secondary Surfactant"), Item("secondary_surfactant_template", editor=InstanceEditor(name='secondary_surfactant_list'), style='custom'), show_labels=False))) # ------------------ # Defaults # ------------------ def _empty_surfactant_template_default(self): return IngredientTemplate(plugin_id=self.id) def __gromacs_database_default(self): return GromacsDatabase() def _surfactant_template_list_default(self): surfactants = self._gromacs_database.get_role('Surfactant') surfactant_templates = [ IngredientTemplate(plugin_id=self.id, ingredient=surfactant) for surfactant in surfactants ] return surfactant_templates def _primary_surfactant_template_default(self): return self.surfactant_template_list[0] def _secondary_surfactant_template_default(self): return self.empty_surfactant_template def _salt_template_default(self): salt = self._gromacs_database.get_ingredient('Sodium Chloride') return IngredientTemplate(plugin_id=self.id, ingredient=salt) def _solvent_template_default(self): water = self._gromacs_database.get_ingredient('Water') return IngredientTemplate(plugin_id=self.id, ingredient=water) # ------------------ # Listeners # ------------------ def _get_id(self): return '.'.join([self.plugin_id, "factory"]) @cached_property def _get_ingredient_templates(self): templates = [ self.primary_surfactant_template, self.salt_template, self.solvent_template ] if self.secondary_surfactant_template.ingredient: templates.insert(1, self.secondary_surfactant_template) return templates @cached_property def _get_n_ingredients(self): return len(self.ingredient_templates) @cached_property def _get_secondary_surfactant_list(self): secondary_list = [self.empty_surfactant_template] secondary_list += [ surfactant for surfactant in self.surfactant_template_list if surfactant != self.primary_surfactant_template ] return secondary_list @on_trait_change('primary_surfactant_template') def update_secondary_surfactant_template(self): if (self.primary_surfactant_template == self.secondary_surfactant_template): self.secondary_surfactant_template = ( self._secondary_surfactant_template_default()) @on_trait_change('plugin_id') def update_plugin_id(self): for surfactant_template in self.surfactant_template_list: surfactant_template.plugin_id = self.id self.empty_surfactant_template.plugin_id = self.id self.salt_template.plugin_id = self.id self.solvent_template.plugin_id = self.id # ------------------ # Public Methods # ------------------ def create_database_templates(self): templates = [] for ingredient_template in self.ingredient_templates: template = ingredient_template.create_template() templates.append(template) return templates def create_formulation_template(self): input_slot_info = [] for template in self.ingredient_templates: if template.ingredient.role != 'Solvent': input_slot_info.append( {"name": f"{template.variable_name}_ingredient"}) input_slot_info.append( {"name": f"{template.variable_name}_conc"}) else: input_slot_info.append( {"name": f"{template.variable_name}_ingredient"}) return { "id": f"{self.id}.formulation", "model_data": { "n_surfactants": self.n_ingredients - 2, "input_slot_info": input_slot_info, "output_slot_info": [{ "name": "formulation" }] } } def create_simulation_template(self): return { "id": f"{self.id}.simulation", "model_data": { "name": "surfactant_experiment", "size": 500, "dry_run": False, "input_slot_info": [{ "name": "formulation" }], "output_slot_info": [{ "name": "results" }] } } def create_micelle_template(self): surfactants = [ template.ingredient for template in self.ingredient_templates if template.ingredient.role == 'Surfactant' ] fragment_symbols = [ surfactant.fragments[0].symbol for surfactant in surfactants ] return { "id": f"{self.id}.micelle", "model_data": { "method": 'atomic', "fragment_symbols": fragment_symbols, "r_thresh": 0.98, "input_slot_info": [{ "name": "formulation" }, { "name": "results" }], "output_slot_info": [{ "name": "micelle" }] } } def create_viscosity_template(self): return { "id": f"{self.id}.viscosity", "model_data": { "input_slot_info": [{ "name": "results" }], "output_slot_info": [{ "name": "viscosity" }] } } def create_cost_template(self): return { "id": f"{self.id}.cost", "model_data": { "input_slot_info": [{ "name": "formulation" }], "output_slot_info": [{ "name": "cost" }] } } def create_template(self): return [{ 'data_sources': self.create_database_templates() }, { 'data_sources': [self.create_formulation_template()] }, { 'data_sources': [self.create_simulation_template()] }, { 'data_sources': [self.create_micelle_template(), self.create_cost_template()] }]
class Workflow(HasStrictTraits): workflow = List(WorkflowItem) backup_workflow = List(WorkflowItem) # for the TASBE task selected = Instance(WorkflowItem) modified = Bool debug = Bool single_operation = View( Item('selected', editor=InstanceEditor(view='operation_traits'), style='custom', show_label=False)) # a view for the entire workflow's list of operations operations_traits = View(Item('workflow', editor=VerticalNotebookEditor( view='operation_traits', page_name='.name', page_description='.friendly_id', page_icon='.icon', delete=True, page_deletable='.deletable', selected='selected', multiple_open=False), show_label=False), scrollable=True) # a view showing the selected workflow item's current view selected_view_traits = View(Item( 'selected', editor=InstanceEditor(view='current_view_traits'), style='custom', show_label=False), Spring(), Item('apply_calls', style='readonly', visible_when='debug'), Item('plot_calls', style='readonly', visible_when='debug'), kind='panel', scrollable=True) # the view for the center pane plot_view = View( Item('selected', editor=InstanceEditor(view='current_plot_view'), style='custom', show_label=False)) plot_params_traits = View( Item('selected', editor=InstanceEditor(view='plot_params_traits'), style='custom', show_label=False)) recv_thread = Instance(threading.Thread) send_thread = Instance(threading.Thread) log_thread = Instance(threading.Thread) remote_process_thread = Instance(threading.Thread) message_q = Instance(Queue, ()) # the Pipe connection object to pass to the matplotlib canvas child_matplotlib_conn = Any # count the number of times the remote process calls apply() or plot(). # useful for debugging apply_calls = Int(0) plot_calls = Int(0) # evaluate an expression in the remote process. useful for debugging. eval_event = Instance(threading.Event, ()) eval_result = Any # evaluate an expression in the remote process. useful for debugging. eval_event = Instance(threading.Event, ()) eval_result = Any def __init__(self, remote_connection, **kwargs): super(Workflow, self).__init__(**kwargs) child_workflow_conn, self.child_matplotlib_conn, log_q = remote_connection self.recv_thread = threading.Thread(target=self.recv_main, name="local workflow recv", args=[child_workflow_conn]) self.recv_thread.daemon = True self.recv_thread.start() self.send_thread = threading.Thread(target=self.send_main, name="local workflow send", args=[child_workflow_conn]) self.send_thread.daemon = True self.send_thread.start() self.log_thread = threading.Thread(target=self.log_main, name="log listener thread", args=[log_q]) self.log_thread.daemon = True self.log_thread.start() def recv_main(self, child_conn): while child_conn.poll(None): try: (msg, payload) = child_conn.recv() except EOFError: return logging.debug("LocalWorkflow.recv_main :: {}".format(msg)) try: if msg == Msg.UPDATE_WI: (idx, name, new) = payload wi = self.workflow[idx] if not wi.trait(name).status: raise RuntimeError( "Tried to set a local non-status wi trait") with wi.lock: wi.trait_set(**{name: new}) elif msg == Msg.UPDATE_OP: (idx, name, new) = payload wi = self.workflow[idx] if not wi.operation.trait(name).status: raise RuntimeError( "Tried to set a local non-status trait") with wi.lock: wi.operation.trait_set(**{name: new}) elif msg == Msg.UPDATE_VIEW: (idx, view_id, name, new) = payload wi = self.workflow[idx] view = next((x for x in wi.views if x.id == view_id)) if not view.trait(name).status: raise RuntimeError( "Tried to set a local non-status trait") with wi.lock: view.trait_set(**{name: new}) elif msg == Msg.APPLY_CALLED: self.apply_calls = payload elif msg == Msg.PLOT_CALLED: self.plot_calls = payload elif msg == Msg.EVAL: self.eval_result = payload self.eval_event.set() else: raise RuntimeError("Bad message from remote") except Exception: log_exception() def send_main(self, child_conn): try: while True: msg = self.message_q.get() child_conn.send(msg) except Exception: log_exception() def log_main(self, log_q): # from http://plumberjack.blogspot.com/2010/09/using-logging-with-multiprocessing.html while True: try: record = log_q.get() if record is None: # We send this as a sentinel to tell the listener to quit. break logger = logging.getLogger(record.name) logger.handle( record) # No level or filter logic applied - just do it! except (KeyboardInterrupt, SystemExit): raise except: print('Whoops! Problem:', file=sys.stderr) traceback.print_exc(file=sys.stderr) def shutdown_remote_process(self): self.message_q.put((Msg.SHUTDOWN, None)) def run_all(self): self.message_q.put((Msg.RUN_ALL, None)) @on_trait_change('workflow') def _on_new_workflow(self, obj, name, old, new): logging.debug("LocalWorkflow._on_new_workflow") self.selected = None # send the new workflow to the child process self.message_q.put((Msg.NEW_WORKFLOW, self.workflow)) @on_trait_change('workflow_items') def _on_workflow_add_remove_items(self, event): logging.debug( "LocalWorkflow._on_workflow_add_remove_items :: {}".format( (event.index, event.removed, event.added))) idx = event.index self.modified = True # remove deleted items from the linked list if event.removed: assert len(event.removed) == 1 removed = event.removed[0] if removed.previous_wi: removed.previous_wi.next_wi = removed.next_wi if removed.next_wi: removed.next_wi.previous_wi = removed.previous_wi self.message_q.put((Msg.REMOVE_ITEMS, idx)) if removed == self.selected: self.selected = None # add new items to the linked list if event.added: assert len(event.added) == 1 if idx > 0: # populate the new wi with metadata from the old one self.workflow[idx].channels = list(self.workflow[idx - 1].channels) self.workflow[idx].conditions = dict( self.workflow[idx - 1].conditions) self.workflow[idx].metadata = dict(self.workflow[idx - 1].metadata) self.workflow[idx].statistics = dict( self.workflow[idx - 1].statistics) self.workflow[idx - 1].next_wi = self.workflow[idx] self.workflow[idx].previous_wi = self.workflow[idx - 1] if idx < len(self.workflow) - 1: self.workflow[idx].next_wi = self.workflow[idx + 1] self.workflow[idx + 1].previous_wi = self.workflow[idx] self.message_q.put((Msg.ADD_ITEMS, (idx, event.added[0]))) @on_trait_change('selected') def _on_selected_changed(self, obj, name, old, new): logging.debug("LocalWorkflow._on_selected_changed :: {}".format( (obj, name, old, new))) if new is None: idx = -1 else: idx = self.workflow.index(new) self.message_q.put((Msg.SELECT, idx)) @on_trait_change('workflow:operation:+') def _operation_changed(self, obj, name, old, new): logging.debug("LocalWorkflow._operation_changed :: {}".format( (obj, name, old, new))) if not obj.trait(name).transient and not obj.trait(name).status: wi = next((x for x in self.workflow if x.operation == obj)) idx = self.workflow.index(wi) self.message_q.put((Msg.UPDATE_OP, (idx, name, new))) self.modified = True @on_trait_change('workflow:operation:changed') def _operation_changed_event(self, obj, _, new): logging.debug("LocalWorkflow._operation_changed_event:: {}".format( (obj, new))) (_, (name, new)) = new wi = next((x for x in self.workflow if x.operation == obj)) idx = self.workflow.index(wi) self.message_q.put((Msg.UPDATE_OP, (idx, name, new))) self.modified = True @on_trait_change('workflow:views:+') def _view_changed(self, obj, name, old, new): logging.debug("LocalWorkflow._view_changed :: {}".format( (obj, name, old, new))) if not obj.trait(name).transient and not obj.trait(name).status: wi = next((x for x in self.workflow if obj in x.views)) idx = self.workflow.index(wi) self.message_q.put((Msg.UPDATE_VIEW, (idx, obj.id, name, new))) self.modified = True @on_trait_change('workflow:views:changed') def _view_changed_event(self, obj, _, new): logging.debug("LocalWorkflow._view_changed_event:: {}".format( (obj, new))) (_, (_, name, new)) = new wi = next((x for x in self.workflow if obj in x.views)) idx = self.workflow.index(wi) self.message_q.put((Msg.UPDATE_VIEW, (idx, obj.id, name, new))) self.modified = True @on_trait_change('workflow:current_view') def _on_current_view_changed(self, obj, name, old, new): logging.debug("LocalWorkflow._on_current_view_changed :: {}".format( (obj, name, old, new))) idx = self.workflow.index(obj) view = obj.current_view self.message_q.put((Msg.CHANGE_CURRENT_VIEW, (idx, view))) @on_trait_change('workflow:current_plot') def _on_current_plot_changed(self, obj, name, old, new): logging.debug("LocalWorkflow._on_current_plot_changed :: {}".format( (obj, name, old, new))) idx = self.workflow.index(obj) plot = obj.current_plot self.message_q.put((Msg.CHANGE_CURRENT_PLOT, (idx, plot))) @on_trait_change('workflow:operation:do_estimate') def _on_estimate(self, obj, name, old, new): logging.debug("LocalWorkflow._on_estimate :: {}".format( (obj, name, old, new))) wi = next((x for x in self.workflow if x.operation == obj)) idx = self.workflow.index(wi) self.message_q.put((Msg.ESTIMATE, idx)) def remote_eval(self, expr): self.eval_event.clear() self.message_q.put((Msg.EVAL, expr)) self.eval_event.wait() return self.eval_result def remote_exec(self, expr): self.message_q.put((Msg.EXEC, expr))