def test_init_dispose(self): # Test init and dispose of the editor. view = View(Item("true_or_false", editor=BooleanEditor())) obj = BoolModel() with reraise_exceptions(), \ create_ui(obj, dict(view=view)): pass
def default_traits_view(self): view = KromView( VGroup( HGroup( Item("model.name", editor=TextEditor(auto_set=True, enter_set=True)), Spring(), Item("model.model_type", style='readonly', label="Type"), Spring(), Item("model.target_product", style='readonly')), VGroup( Item("model.is_kinetic", label="Is Kinetic", editor=BooleanEditor()), VGroup( Item('param_formula_str', style="readonly", show_label=False), Item("component_array", label="Comp. Coeff.", show_label=False, editor=self._tabular_editor), ), label="Parameters", show_border=True, ), ), # Relevant when used as standalone view: resizable=True, buttons=OKCancelButtons, default_button=OKButton, title="Configure {} model".format(self.model.model_type)) return view
class CubeAxesActor2D(tvtk.CubeAxesActor2D): """ Just has a different view than the tvtk.CubesAxesActor2D, with an additional tick box. """ # Automaticaly fit the bounds of the axes to the data use_data_bounds = true input_info = PipelineInfo(datasets=['any'], attribute_types=['any'], attributes=['any']) ######################################## # The view of this object. traits_view = View(Group( Group( Item('visibility'), HGroup( Item('x_axis_visibility', label='X axis'), Item('y_axis_visibility', label='Y axis'), Item('z_axis_visibility', label='Z axis'), ), show_border=True, label='Visibity'), Group( Item('use_ranges'), HGroup( Item('ranges', enabled_when='use_ranges'), ), show_border=True), Group( Item('use_data_bounds'), HGroup( Item('bounds', enabled_when='not use_data_bounds'), ), show_border=True), Group( Item('x_label'), Item('y_label'), Item('z_label'), Item('label_format'), Item('number_of_labels'), Item('font_factor'), show_border=True), HGroup(Item('show_actual_bounds', label='Use size bigger than screen', editor=BooleanEditor())), Item('fly_mode'), Item('corner_offset'), Item('layer_number'), springy=True, ), scrollable=True, resizable=True, )
def check_click_boolean_changes_trait(self, style): view = View(Item("true_or_false", style=style, editor=BooleanEditor())) obj = BoolModel() tester = UITester() with tester.create_ui(obj, dict(view=view)) as ui: # sanity check self.assertEqual(obj.true_or_false, False) checkbox = tester.find_by_name(ui, "true_or_false") checkbox.perform(MouseClick()) self.assertEqual(obj.true_or_false, True)
def default_traits_view(self): view = View( VGroup(Item("model.number_user_solution_points", label="Number of Time Points in Solution"), Item("model.nthreads", label="Number of Solver Threads"), Item("model.write_solution_all", label="Save Solution for all Discretization Points", editor=BooleanEditor()), Item("model.use_analytic_jacobian", label="Use Analytic Jacobian", editor=BooleanEditor()), label="Solver Setup", show_border=True), VGroup(Item("model.time_integrator.abstol", label="Absolute Yolerance in the Solution", editor=PositiveFloatEditor()), Item("model.time_integrator.init_step_size", label="Initial Step Size Factor", editor=PositiveFloatEditor()), Item("model.time_integrator.max_steps", label="Maximum Number of Timesteps", editor=PositiveIntEditor()), Item("model.time_integrator.reltol", label="Relative Tolerance in the Solution", editor=PositiveFloatEditor()), label="Time Integrator Parameters", show_border=True), VGroup(Item("model.schur_solver.gs_type", label="Type of Gram-Schmidt Orthogonalization"), Item("model.schur_solver.max_krylov", label="Size of the Iterative Linear SPGMR Solver"), Item("model.schur_solver.max_restarts", label="Maximum Number of GMRES Restarts", editor=PositiveIntEditor()), Item("model.schur_solver.schur_safety", label="Schur Safety Factor", editor=PositiveFloatEditor()), label="Schur Solver Parameters", show_border=True), ) return view
def get_editor(self, trait): # Make the special case of a 'bool' type use the boolean editor: if self.aType is bool: if self.editor is None: from traitsui.api import BooleanEditor self.editor = BooleanEditor() return self.editor # Otherwise, map all other types to a text editor: auto_set = trait.auto_set if auto_set is None: auto_set = True from traitsui.api import TextEditor return TextEditor( auto_set=auto_set, enter_set=trait.enter_set or False, evaluate=self.fast_validate[1], )
class Controller(HasTraits, BaseControllerImpl): ATTRIBUTES = collections.OrderedDict(( ('w', IndexAttribute('w')), ('x', IndexAttribute('x')), ('y', IndexAttribute('y')), ('z', IndexAttribute('z')), ('colormap', ColormapAttribute()), ('colorbar', ColorbarAttribute()), ('slicing_axis', Dimension4DAttribute()), ('clip', ClipAttribute()), ('clip_min', ClipAttribute()), ('clip_max', ClipAttribute()), ('clip_auto', AutoClipAttribute()), ('clip_symmetric', SymmetricClipAttribute()), # passed to views ('locate_mode', LocateModeAttribute()), ('locate_value', LocateValueAttribute()), )) DIMENSIONS = "2D, 3D, ..., nD" DATA_CHECK = classmethod(lambda cls, data: len(data.shape) >= 2) DESCRIPTION = """\ Controller for multiple views """ LABEL_WIDTH = 30 LOCAL_AXIS_NAMES = ['x', 'y', 'z'] LOCAL_AXIS_NUMBERS = dict((axis_name, axis_number) for axis_number, axis_name in enumerate(LOCAL_AXIS_NAMES)) GLOBAL_AXIS_NAMES = ['w', 'x', 'y', 'z'] GLOBAL_AXIS_NUMBERS = dict((axis_name, axis_number) for axis_number, axis_name in enumerate(GLOBAL_AXIS_NAMES)) S_ALL = slice(None, None, None) # The axis selectors ## w: w_low = Int(0) w_high = Int(0) w_index = Int(0) w_range = Range(low='w_low', high='w_high', value='w_index') ## x: x_low = Int x_high = Int x_index = Int x_range = Range(low='x_low', high='x_high', value='x_index') ## y: y_low = Int(0) y_high = Int(0) y_index = Int(0) y_range = Range(low='y_low', high='y_high', value='y_index') ## z: z_low = Int(0) z_high = Int(0) z_index = Int(0) z_range = Range(low='z_low', high='z_high', value='z_index') ## is4D: is4D = Bool() slicing_axis = Str("") data_shape = Str("") close_button = Action(name='Close', action='_on_close') colorbar = Bool() colormap = Str() data_min = Float() data_max = Float() clip = Float() clip_min = Float() clip_max = Float() clip_auto = Bool() clip_symmetric = Bool() clip_readonly = Bool() clip_visible = Bool() clip_range_readonly = Bool() clip_range_visible = Bool() def __init__(self, logger, attributes, title=None, **traits): HasTraits.__init__(self, **traits) BaseControllerImpl.__init__(self, logger=logger, title=title, attributes=attributes) self.w_low, self.x_low, self.y_low, self.z_low = 0, 0, 0, 0 rank = len(self.shape) if rank == 2: wh = 1 zh = 1 xh, yh = self.shape elif rank == 3: wh = 1 xh, yh, zh = self.shape elif rank == 4: wh, xh, yh, zh = self.shape self.w_high, self.x_high, self.y_high, self.z_high = wh - 1, xh - 1, yh - 1, zh - 1 self.set_default_clips() self.set_axis_mapping() ### U t i l i t i e s : def create_view(self, view_class, data, title=None): return view_class(controller=self, data=data, title=title) def add_view(self, view_class, data, title=None): if data.shape != self.shape: raise ValueError("{}: cannot create {} view: data shape {} is not {}".format(self.name, view_class.__name__, data.shape, self.shape)) local_volume = self.get_local_volume(data) view = self.create_view(view_class, local_volume, title=title) self.set_view_axis(view) self.views.append(view) self.views_data[view] = data #self.add_class_trait(view.name, Instance(view_class)) #self.add_trait(view.name, view) self.update_data_range() return view def set_view_axis(self, view): for global_axis_name in self.GLOBAL_AXIS_NAMES: if global_axis_name != self.slicing_axis: local_axis_name = self.get_local_axis_name(global_axis_name) setattr(view, "{}_index".format(local_axis_name), getattr(self, "{}_index".format(global_axis_name))) def update_data_range(self): if self.views: data_min_l, data_max_l = [], [] for view in self.views: for local_volume in view.data: data_min_l.append(local_volume.min()) data_max_l.append(local_volume.max()) self.data_min = float(min(data_min_l)) self.data_max = float(max(data_max_l)) self.logger.info("{}: data range: {} <-> {}".format(self.name, self.data_min, self.data_max)) else: self.data_min = 0.0 self.data_max = 0.0 def get_local_volume(self, data): if data.shape != self.shape: raise ValueError("{}: invalid shape {}".format(self.name, data.shape)) if len(self.shape) == 4: s = [self.S_ALL, self.S_ALL, self.S_ALL] s.insert(self.GLOBAL_AXIS_NUMBERS[self.slicing_axis], getattr(self, '{}_index'.format(self.slicing_axis))) return data[s] else: return data def set_axis_mapping(self): self._m_local2global = {} self._m_global2local = {} local_axis_number = 0 for global_axis_number, global_axis_name in enumerate(self.GLOBAL_AXIS_NAMES): if global_axis_name != self.slicing_axis: local_axis_name = self.LOCAL_AXIS_NAMES[local_axis_number] self._m_local2global[local_axis_name] = global_axis_name self._m_global2local[global_axis_name] = local_axis_name local_axis_number += 1 self.logger.info("{}: local2global: {}".format(self.name, self._m_local2global)) self.logger.info("{}: global2local: {}".format(self.name, self._m_global2local)) def get_global_axis_name(self, local_axis_name): return self._m_local2global[local_axis_name] def get_local_axis_name(self, global_axis_name): return self._m_global2local[global_axis_name] def set_default_clips(self): clip_symmetric = self.attributes["clip_symmetric"] clip_auto = self.attributes["clip_auto"] clip = self.attributes["clip"] clip_min = self.attributes["clip_min"] clip_max = self.attributes["clip_max"] if clip is not None: self.clip = clip if clip_min is not None: self.clip_min = clip_min if clip_max is not None: self.clip_max = clip_max if clip_symmetric is None: if clip is not None: clip_symmetric = True else: clip_symmetric = False if clip_auto is None: if clip is None and (clip_min is None or clip_max is None): clip_auto = True else: clip_auto = False self.clip_symmetric = clip_symmetric self.clip_auto = clip_auto def close_uis(self): # locks on exit ! super(Controller, self).close_uis() def close_views(self): for view in self.views: view.close_uis() del self.views[:] ### D e f a u l t s : def _colorbar_default(self): return self.attributes["colorbar"] def _colormap_default(self): return self.attributes["colormap"] def _data_shape_default(self): return 'x'.join(str(d) for d in self.shape) def _is4D_default(self): return len(self.shape) == 4 def _axis_index_default(self, axis_name): if self.attributes.get(axis_name, None) is None: h = getattr(self, "{}_high".format(axis_name)) l = getattr(self, "{}_low".format(axis_name)) return (h - l) // 2 else: return self.attributes[axis_name] def _w_index_default(self): return self._axis_index_default('w') def _x_index_default(self): return self._axis_index_default('x') def _y_index_default(self): return self._axis_index_default('y') def _z_index_default(self): return self._axis_index_default('z') def _slicing_axis_default(self): slicing_axis = self.attributes.get("slicing_axis", None) if slicing_axis is None: slicing_axis = self.GLOBAL_AXIS_NAMES[0] return slicing_axis def on_change_axis(self, global_axis_name): global_attribute = '{}_index'.format(global_axis_name) self.log_trait_change(global_attribute) if global_axis_name == self.slicing_axis: #self.logger.error("{}: changing the slicing axis is not supported yet".format(self.name)) for view in self.views: local_volume = self.get_local_volume(self.views_data[view]) view.set_volume(local_volume) self.update_data_range() self.update_clip_range() else: local_axis_name = self.get_local_axis_name(global_axis_name) local_attribute = '{}_index'.format(local_axis_name) self.apply_attribute(local_attribute, getattr(self, global_attribute)) def set_clip(self): if self.clip_auto: self.clip_min, self.clip_max = self.data_min, self.data_max self.clip = max(abs(self.clip_min), abs(self.clip_max)) self.clip_readonly = not self.clip_auto self.clip_range_readonly = not self.clip_auto self.clip_visible = self.clip_symmetric self.clip_range_visible = not self.clip_symmetric def get_clip_range(self): if self.clip_auto: clip_min = float(self.data_min) clip_max = float(self.data_max) clip = max(abs(clip_min), abs(clip_max)) else: clip_min = self.clip_min clip_max = self.clip_max clip = self.clip if self.clip_symmetric: return -clip, clip else: return clip_min, clip_max def update_clip_range(self): clip_min, clip_max = self.get_clip_range() self.logger.info("{}: applying clip {} <-> {}".format(self.name, clip_min, clip_max)) for view in self.views: view.update_clip_range(clip_min, clip_max) ### T r a t s c h a n g e s : @on_trait_change('colorbar') def on_change_colorbar(self): for view in self.views: view.enable_colorbar(self.colorbar) @on_trait_change('colormap') def on_change_colormap(self): for view in self.views: view.set_colormap(self.colormap) @on_trait_change('data_min,data_max') def on_change_data_range(self): self.set_clip() self.update_clip_range() @on_trait_change('clip_symmetric') def on_change_clip_symmetric(self): self.set_clip() self.update_clip_range() @on_trait_change('clip_auto') def on_change_clip_auto(self): self.set_clip() self.update_clip_range() @on_trait_change('clip') def on_change_clip(self): self.logger.debug("{}: clip: auto={}, symmetric={}, clip={}".format(self.name, self.clip_auto, self.clip_symmetric, self.clip)) if self.clip_symmetric: self.update_clip_range() @on_trait_change('clip_min,clip_max') def on_change_clip(self): self.logger.debug("{}: clip: auto={}, symmetric={}, clip_min={}, clip_max={}".format(self.name, self.clip_auto, self.clip_symmetric, self.clip_min, self.clip_max)) if not self.clip_symmetric: self.update_clip_range() @on_trait_change('w_index') def on_change_w_index(self): self.on_change_axis('w') @on_trait_change('x_index') def on_change_x_index(self): self.on_change_axis('x') @on_trait_change('y_index') def on_change_y_index(self): self.on_change_axis('y') @on_trait_change('z_index') def on_change_z_index(self): self.on_change_axis('z') controller_view = View( HGroup( Group( Item( 'data_shape', label="Shape", style="readonly", ), Item( 'slicing_axis', editor=EnumEditor( values=GLOBAL_AXIS_NAMES ), label="Slicing dim", enabled_when='is4D', visible_when='is4D', #emphasized=True, style="readonly", tooltip="the slicing dimension", help="4D volumes are sliced along the 'slicing dimension'; it is possible to change the value of this dimension using the related slider", ), Item( '_', enabled_when='is4D', visible_when='is4D', ), Item( 'w_index', editor=RangeEditor( enter_set=True, low_name='w_low', high_name='w_high', format="%d", #label_width=LABEL_WIDTH, mode="auto", ), enabled_when='is4D', visible_when='is4D', tooltip="the w dimension", ), Item( 'x_index', editor=RangeEditor( enter_set=True, low_name='x_low', high_name='x_high', format="%d", #label_width=LABEL_WIDTH, mode="slider", ), format_str="%<8s", tooltip="the x dimension", ), Item( 'y_index', editor=RangeEditor( enter_set=True, low_name='y_low', high_name='y_high', format="%d", #label_width=LABEL_WIDTH, mode="slider", ), format_str="%<8s", tooltip="the y dimension", ), Item( 'z_index', editor=RangeEditor( enter_set=True, low_name='z_low', high_name='z_high', format="%d", #label_width=LABEL_WIDTH, mode="slider", ), format_str="%<8s", tooltip="the z dimension", ), '_', Item( 'colorbar', editor=BooleanEditor( ), label="Colorbar", ), Item( 'colormap', editor=EnumEditor( values=COLORMAPS, ), label="Colormap", ), '_', Item( 'data_min', label="Data min", style="readonly", ), Item( 'data_max', label="Data max", style="readonly", ), Item( 'clip_auto', editor=BooleanEditor(), label="Automatic", tooltip="makes clip automatic", help="if set, clip is taken from data range" ), Item( 'clip_symmetric', editor=BooleanEditor(), label="Symmetric", tooltip="makes clip symmetric", help="if set, clip_min=-clip, clip_max=+clip", ), Item( 'clip', label="Clip", visible_when='clip_visible', enabled_when='clip_readonly', ), Item( 'clip_min', label="Clip min", visible_when='clip_range_visible', enabled_when='clip_range_readonly', ), Item( 'clip_max', label="Clip max", visible_when='clip_range_visible', enabled_when='clip_range_readonly', ), ), ), buttons=[UndoButton, RevertButton, close_button], handler=ControllerHandler(), resizable=True, title="untitled", )
class ControlPanel(HasTraits): ''' This object is the core of the traitsUI interface. Its view is the right panel of the application, and it hosts the method for interaction between the objects and the GUI. ''' ################################### # ======== Panel GUI ======== # bool switch, to enable/disable params changing params_allow_change = Bool # ---- Control tab ---- button_start_stop_simulation = Button("Start/Stop simulation") # Range of simulation area (length * width * height), unit: meter area_length, area_width, area_height = Int, Int, Int # sim time interval sim_dt = Float # sim scene display switch sim_scene_switch = Bool # record switch sim_record_switch = Bool # simulation step count text_sim_step_count = Int # simulation time count text_sim_time_count = String # button for camera info saving button_save_camera_angle = Button("Save camera angle") # select local/distributed communication # local: srsim & algorithm plat are running on a same machine # distributed: srsim & algorithm plat are running on different machines enum_comm_mode = Enum('local', 'distributed') # communication address text_comm_address = String # communication port text_comm_port = String # ---- Wind tab ---- # Wind = Advective flow + Turbulent flow # (1) Advective flow # Selection of advective flow model enum_advection_model = Enum('constant', 'uniform', 'irrotational', 'ext') # Advection Model 1: constant advective flow field # wind vector at every point is 'advection_mean' # Advection Model 2: uniform advective flow field # wind vector at every point is the same as 'advection_mean'+stochastic variance # Advection Model 3: irrotational, incompressible flow # wind vector at every point is different, vectors at vertex points are different # 'advection_mean'+stochastic variance, and vectors at six faces (cuboid flow area) # are interpolated from vertex vectors (Boundary Conditions). And the rest points are # calculated via laplacian equation advection_mean_x, advection_mean_y, \ advection_mean_z = Float, Float, Float wind_colored_noise_g, wind_colored_noise_xi, wind_colored_noise_omega = \ Float, Float, Float # Advection Model 3: load external advection field data # ---- Plume tab ---- enum_plume_model = Enum('farrell', 'other') init_odor_source_pos_x, init_odor_source_pos_y,\ init_odor_source_pos_z = Float, Float, Float fila_release_per_sec = Int fila_centerline_dispersion_sigma = Float fila_growth_rate = Float # ---- Robot tab ---- init_robot_pos_x, init_robot_pos_y, \ init_robot_pos_z = Float, Float, Float # ---- Dispay ---- # simulator running state display textbox_sim_state_display = String() ################################### # ======== Data & Params ======== # Data can be updated from outside # ---- simulation field grid, which is Tuple of x, y, z arrays grid = Tuple(Array, Array, Array) gsize = Float # grid size # ---- mean wind flow vector instantaneously mean_wind_vector = Tuple(Float, Float, Float) ################################### # ======== Events ======== # results draw update event event_need_update_scene = Event ################################### # ======== Streamlines ======== wind_field = None # vector field, mayavi odor_field = None # scatter, mayavi odor_source = None # use visual to display a cylinder as the source robot_draw = None # use visual.frame to draw a robot ################################### # ======== Other ======== # simulation thread sim_thread = Instance(SimulationThread) scene = Instance(MlabSceneModel) ################################### # view view = View(VSplit( Group( # Control tab Group( Group( Item('area_length', editor = RangeEditor( low = '1', high = '50', format = '%d', mode = 'slider'), label = 'L (meters)'), Item('area_width', editor = RangeEditor( low = '1', high = '50', format = '%d', mode = 'slider'), label = 'W (meters)'), Item('area_height', editor = RangeEditor( low = '1', high = '50', format = '%d', mode = 'slider'), label = 'H (meters)'), label = 'Area Size L/W/H', show_border=True, enabled_when = "params_allow_change == True"), Item('sim_dt', editor = RangeEditor( low = '0.01', high = '0.1', format = '%.2f', mode = 'slider'), label = 'delta t (second)', enabled_when = "params_allow_change == True"), HGroup( Item('button_save_camera_angle', \ show_label = False), Item('sim_scene_switch', editor = BooleanEditor( mapping={ "on":True, "off":False}), label = 'Scene switch'), Item('sim_record_switch', editor = BooleanEditor( mapping={ "on":True, "off":False}), label = 'Record switch'), enabled_when = "params_allow_change == True"), VGroup( Item('text_sim_step_count', editor = TextEditor( auto_set = False, enter_set = False), label = 'Step', style = 'readonly'), Item('text_sim_time_count', editor = TextEditor( auto_set = False, enter_set = False), label = 'Time', style = 'readonly'), label = 'Simulation Step /& Time', show_border=True), Item('button_start_stop_simulation', \ show_label = False), Group( Item(name = 'enum_comm_mode', editor = EnumEditor(values = { 'local' : '1:Local: 1 machine', 'distributed' : '2:Distributed: 2 machines',}), label = 'Communication', style = 'simple'), Item('text_comm_address', editor = TextEditor( auto_set = True, enter_set = True), label = 'Address to bind', style = 'simple', enabled_when = "enum_comm_mode == 'distributed'"), Item('text_comm_port', editor = TextEditor( auto_set = True, enter_set = True), label = 'Port to bind', style = 'simple', enabled_when = "enum_comm_mode == 'distributed'"), label = 'Communication (Note: NEED REBOOT)', show_border=True), label = 'Control', dock = 'tab'), # Wind tab Group( Item(name = 'enum_advection_model', editor = EnumEditor(values = { 'constant' : '1:Constant Flow (Time Invariant)', 'uniform' : '2:Uniform Flow (Time Variant)', 'irrotational' : '3:Irrotational & Incompressible', 'ext' : '4:Load external data',}), label = 'Advection', style = 'simple'), Group( Item('advection_mean_x', editor = RangeEditor( low = '-10.0', high = '10.0', format = '%.1f', mode = 'slider'), label = 'x (m/s):',), Item('advection_mean_y', editor = RangeEditor( low = '-10.0', high = '10.0', format = '%.1f', mode = 'slider'), label = 'y (m/s):',), Item('advection_mean_z', editor = RangeEditor( low = '-10.0', high = '10.0', format = '%.1f', mode = 'slider'), label = 'z (m/s):',), label = 'Mean wind vector', show_border=True, visible_when = "enum_advection_model == 'irrotational' or \ enum_advection_model == 'uniform' or enum_advection_model == 'constant'" ), Group( Item('wind_colored_noise_g', editor = RangeEditor( low = '0.0', high = '10.0', format = '%.1f', mode = 'slider'), label = 'G',), Item('wind_colored_noise_xi', editor = RangeEditor( low = '0.0', high = '2.0', format = '%.01f', mode = 'slider'), label = 'Damping',), Item('wind_colored_noise_omega', editor = RangeEditor( low = '0.0', high = '2.0', format = '%.01f', mode = 'slider'), label = 'Bandwidth',), label = 'Stochastic (colored noise) params', show_border=True, visible_when = "enum_advection_model == 'irrotational' or enum_advection_model == 'uniform'"), label = 'Wind', dock = 'tab', enabled_when = "params_allow_change == True"), # Plume tab Group( Item(name = 'enum_plume_model', editor = EnumEditor(values = { 'farrell' : '1:Farrell plume model', 'other' : '2:Other plume model...',}), style = 'custom'), Group( Item('init_odor_source_pos_x', editor = RangeEditor( low = '0.0', high_name = 'area_length', format = '%.1f', mode = 'auto'), label = 'x (m):',), Item('init_odor_source_pos_y', editor = RangeEditor( low = '0.0', high_name = 'area_width', format = '%.1f', mode = 'auto'), label = 'y (m):',), Item('init_odor_source_pos_z', editor = RangeEditor( low = '0.0', high_name = 'area_height', format = '%.1f', mode = 'auto'), label = 'z (m):',), label = 'Odor source position (x,y,z) (m)', show_border=True), Item('fila_centerline_dispersion_sigma', editor = RangeEditor( low = '0.0', high = '10.0', format = '%.1f', mode = 'slider'), label = 'Sigma (m/s/sqr(Hz))', visible_when = "enum_plume_model == 'farrell'"), Item('fila_growth_rate', editor = RangeEditor( low = '0.0', high = '1.0', format = '%.3f', mode = 'slider'), label = 'Growth rate (meter/sec)', visible_when = "enum_plume_model == 'farrell'"), Item('fila_release_per_sec', editor = RangeEditor( low = '1', high = '100', format = '%d', mode = 'slider'), label = 'Release rate (fila number/sec)', visible_when = "enum_plume_model == 'farrell'"), label = 'Plume', dock = 'tab', enabled_when = "params_allow_change == True"), # Robot tab Group( Group( Item('init_robot_pos_x', editor = RangeEditor( low = '0.0', high_name = 'area_length', format = '%.1f', mode = 'auto'), label = 'x (m):',), Item('init_robot_pos_y', editor = RangeEditor( low = '0.0', high_name = 'area_width', format = '%.1f', mode = 'auto'), label = 'y (m):',), Item('init_robot_pos_z', editor = RangeEditor( low = '0.0', high_name = 'area_height', format = '%.1f', mode = 'auto'), label = 'z (m):',), label = 'Robot position (x,y,z) (m)', show_border=True), label = 'Robot', dock = 'tab', enabled_when = "params_allow_change == True"), layout = 'tabbed'), Item(name = 'textbox_sim_state_display', label = 'Simulator State', show_label=False, resizable=True, springy=True, style='custom') ) ) ################################### # ======== Init functions ======== def __init__(self, **traits): HasTraits.__init__(self, **traits) # Area size default: 10.0x10.0x10.0m def _area_length_default(self): size = Config.get_sim_area_size() return size[0] def _area_width_default(self): size = Config.get_sim_area_size() return size[1] def _area_height_default(self): size = Config.get_sim_area_size() return size[2] def _sim_dt_default(self): return Config.get_dt() def _sim_scene_switch_default(self): return True def _sim_record_switch_default(self): return False def _params_allow_change_default(self): return True def _enum_comm_mode_default(self): mode = Config.get_comm_mode() return mode def _text_comm_address_default(self): addr = Config.get_comm_address() return addr def _text_comm_port_default(self): port = Config.get_comm_port() return port def _enum_advection_model_default(self): model = Config.get_wind_model() return model def _advection_mean_x_default(self): return self.mean_wind_vector[0] def _advection_mean_y_default(self): return self.mean_wind_vector[1] def _advection_mean_z_default(self): return self.mean_wind_vector[2] def _wind_colored_noise_g_default(self): noise_params = Config.get_wind_colored_noise_params() return noise_params[0] def _wind_colored_noise_xi_default(self): noise_params = Config.get_wind_colored_noise_params() return noise_params[1] def _wind_colored_noise_omega_default(self): noise_params = Config.get_wind_colored_noise_params() return noise_params[2] def _init_odor_source_pos_x_default(self): pos = Config.get_odor_source_pos() return pos[0] def _init_odor_source_pos_y_default(self): pos = Config.get_odor_source_pos() return pos[1] def _init_odor_source_pos_z_default(self): pos = Config.get_odor_source_pos() return pos[2] def _init_robot_pos_x_default(self): pos = Config.get_robot_init_pos() return pos[0] def _init_robot_pos_y_default(self): pos = Config.get_robot_init_pos() return pos[1] def _init_robot_pos_z_default(self): pos = Config.get_robot_init_pos() return pos[2] def _gsize_default(self): return Config.get_wind_grid_size() def _grid_default(self): x, y, z = np.mgrid[self.gsize / 2.0:self.area_length:self.gsize, self.gsize / 2.0:self.area_width:self.gsize, self.gsize / 2.0:self.area_height:self.gsize] return x, y, z def _mean_wind_vector_default(self): vector = Config.get_mean_wind_vector() return vector[0], vector[1], vector[2] def _fila_centerline_dispersion_sigma_default(self): params = Config.get_plume_model_params() return params[0] def _fila_growth_rate_default(self): params = Config.get_plume_model_params() return params[1] def _fila_release_per_sec_default(self): params = Config.get_plume_model_params() return params[2] def _text_sim_step_count_default(self): return 0 def _text_sim_time_count_default(self): return '0.0 s' ################################### # ======== Listening ======== @on_trait_change('scene.activated') def init_scene(self): # init wind field object, mayavi self.func_wind_field_obj_init() # init odor source pos object, visual visual.set_viewer( self.scene) # tell visual to use this scene as the viewer op = Config.get_odor_source_pos() # get init odor source pos self.odor_source = visual.box(pos=(op[0], op[1], op[2]/2.0), length = 0.1, \ height = 0.1, width = op[2], color = (0x05/255.0, 0x5f/255.0,0x58/255.0), ) # init robot drawing obj, visual rp = Config.get_robot_init_pos() self.func_init_robot_shape(rp) # init wind vane obj, visual, indicating mean wind vector self.func_init_wind_vane() # axes and outlines self.func_init_axes_outline() # camera view self.scene.mlab.view(*Config.get_camera_view()) # check if TCP/IP address/port is successfully bind if self.objs_comm_process['sim_state'][1] == 1: if self.enum_comm_mode == 'local': self.textbox_sim_state_display = 'Listenting to localhost:60000' elif self.enum_comm_mode == 'distributed': self.textbox_sim_state_display = 'Listenting to ' \ + self.text_comm_address + ':' + self.text_comm_port else: self.textbox_sim_state_display = "Listenting to what? I don't know" elif self.objs_comm_process['sim_state'][1] == -1: if self.enum_comm_mode == 'local': self.textbox_sim_state_display = 'Error: Cannot bind localhost:60000' elif self.enum_comm_mode == 'distributed': self.textbox_sim_state_display = 'Error: Cannot bind ' \ + self.text_comm_address + ':' + self.text_comm_port else: self.textbox_sim_state_display = "Error: Can't bind what? I forgot" @on_trait_change('area_length, area_width, area_height') def change_area_size(self): # change mesh grid to new shape x, y, z = np.mgrid[self.gsize/2.0:self.area_length:self.gsize, \ self.gsize/2.0:self.area_width:self.gsize, self.gsize/2.0:self.area_height:self.gsize] self.grid = x, y, z # reset wind_field object, mayavi self.scene.disable_render = True self.scene.mlab.clf() # clean figure self.wind_field.remove() self.func_wind_field_obj_init() # update axes & outline, mayavi self.func_init_axes_outline() self.scene.disable_render = False # save area setting to global settings Config.set_sim_area_size( [self.area_length, self.area_width, self.area_height]) @on_trait_change('sim_dt') def change_dt(self): # save sim dt to global settings Config.set_dt(self.sim_dt) @on_trait_change('enum_comm_mode') def change_comm_mode(self): Config.set_comm_mode(self.enum_comm_mode) @on_trait_change('text_comm_address') def change_comm_address(self): Config.set_comm_address(self.text_comm_address) @on_trait_change('text_comm_port') def change_comm_port(self): Config.set_comm_port(self.text_comm_port) @on_trait_change('enum_advection_model') def change_advection_model_selection(self): # save selection to global settings Config.set_wind_model(self.enum_advection_model) @on_trait_change('advection_mean_x, advection_mean_y, \ advection_mean_z') def change_advection_mean_vector(self): self.mean_wind_vector = self.advection_mean_x,\ self.advection_mean_y, self.advection_mean_z # save mean wind vector setting to global settings Config.set_mean_wind_vector(list(self.mean_wind_vector)) # change wind vane obj, visual self.func_change_wind_vane(self.mean_wind_vector) @on_trait_change( 'wind_colored_noise_g, wind_colored_noise_xi, wind_colored_noise_omega' ) def change_wind_colored_noise_params(self): # save wind colored noise params to global settings Config.set_wind_colored_noise_params([self.wind_colored_noise_g, \ self.wind_colored_noise_xi, self.wind_colored_noise_omega]) @on_trait_change( 'init_odor_source_pos_x, init_odor_source_pos_y, init_odor_source_pos_z' ) def change_init_odor_source_pos(self): # change the pos of odor source obj x, y, z = self.init_odor_source_pos_x,\ self.init_odor_source_pos_y, self.init_odor_source_pos_z self.odor_source.pos = (x, y, z / 2.0) self.odor_source.width = z # save pos setting to global settings Config.set_odor_source_pos([x, y, z]) @on_trait_change('init_robot_pos_x, init_robot_pos_y, init_robot_pos_z') def change_init_robot_pos(self): # change the pos of robot drawing obj x, y, z = self.init_robot_pos_x, self.init_robot_pos_y, self.init_robot_pos_z self.robot_draw.pos = (x, y, z) # save pos setting to global settings Config.set_robot_init_pos([x, y, z]) @on_trait_change( 'fila_release_per_sec, fila_centerline_dispersion_sigma, fila_growth_rate' ) def change_farrell_params(self): # save farrell params to global settings Config.set_plume_model_params([self.fila_centerline_dispersion_sigma, \ self.fila_growth_rate, self.fila_release_per_sec]) def _button_start_stop_simulation_fired(self): ''' Callback of the "start/stop simulation" button, this starts the simulation thread, or kills it ''' if self.sim_thread and self.sim_thread.isAlive(): # kill simulation thread if it's running self.sim_thread.wants_abort = True # enable area size changing item (GUI) self.params_allow_change = True # update sim state indicator, tell communication process the sim will end self.objs_comm_process['sim_state'][0] = 0 else: # disable area size changing item (GUI) self.params_allow_change = False # check if need re-init scene if self.sim_thread: # if sim_thread != None, i.e., if sim_thread once excecuted # re-init scene self.wind_field.remove() self.func_wind_field_obj_init() self.odor_field.remove() # print simulator settings & clear sim step/time count self.textbox_sim_state_display = '' # clear display sim_area_str = 'Sim Area (L*W*H): %.1f m * %.1f m * %.1f m\n\ Grid size: %.1f m' % (self.area_length, self.area_width, self.area_height, Config.get_wind_grid_size()) sim_wind_str = 'Wind type: ' + str(self.enum_advection_model) + '\n' \ + ' Mean wind vector = ' + str(self.mean_wind_vector) if self.enum_advection_model == 'irrotational' or self.enum_advection_model == 'uniform': sim_wind_str += '\n' + ' Colored noise Params: ' + '\n' \ + ' G = ' + str(self.wind_colored_noise_g) + '\n' \ + ' wind_damping = ' + str(self.wind_colored_noise_xi) + '\n' \ + ' wind_bandwidth = ' + str(self.wind_colored_noise_omega) self.add_text_line('====== Settings ======' + '\n' + sim_area_str + '\n' + sim_wind_str + '\n' + '====== Simulation started ======') # clear sim step/time count self.text_sim_step_count = 0 self.text_sim_time_count = '0.0 s' # --- create & start simulation thread self.sim_thread = SimulationThread() # --- Params convert to sim thread self.sim_thread.stop_simulation = self.func_sim_thread_killed_itself self.sim_thread.sim_dt = self.sim_dt # for sim thread use to calculate sim time self.sim_thread.sim_area_size = [ self.area_length, self.area_width, self.area_height ] self.sim_thread.odor_source_pos = [self.init_odor_source_pos_x,\ self.init_odor_source_pos_y, self.init_odor_source_pos_z] self.sim_thread.wind_gsize = self.gsize self.sim_thread.wind_mesh = self.grid self.sim_thread.wind_model = self.enum_advection_model self.sim_thread.wind_mean_flow = self.mean_wind_vector self.sim_thread.wind_G = self.wind_colored_noise_g self.sim_thread.wind_damping = self.wind_colored_noise_xi self.sim_thread.wind_bandwidth = self.wind_colored_noise_omega self.sim_thread.plume_vm_sigma = self.fila_centerline_dispersion_sigma self.sim_thread.plume_fila_growth_rate = self.fila_growth_rate self.sim_thread.plume_fila_number_per_sec = self.fila_release_per_sec self.sim_thread.robot_init_pos = [self.init_robot_pos_x, \ self.init_robot_pos_y, self.init_robot_pos_z] self.sim_thread.objs_comm_process = self.objs_comm_process # simulation scene update function, it triggs an event to update mayavi streamline self.sim_thread.update_scene = self.func_event_update_scene # simulation state text function self.sim_thread.add_text_line = self.add_text_line # -- init odor field object, scatter, mayavi self.odor_field = self.scene.mlab.points3d([0.0], [0.0], [0.0], [0.0], \ color = (0,0,0), scale_factor=1, reset_zoom=False) # create record data directory if record switch is turned on (default off) if self.sim_record_switch == True: # create a directory named as: srsim_record_[date]_[time] p = sys.path[0] # dir of this script self.record_dir = p[0:p.index('SRSim/srsim')] + 'srsim_record_' + \ time.strftime('%Y-%m-%d_%H%M%S',time.localtime(time.time())) os.mkdir(self.record_dir) # create file containing wind grid/mesh info sio.savemat(self.record_dir + '/wind_mesh.mat', {'wind_mesh': self.grid}) # --- start simulation thread self.sim_thread.start() # update sim state indicator, tell communication process the sim started self.objs_comm_process['sim_state'][0] = 1 def _event_need_update_scene_fired(self): # count sim step/time self.func_sim_step_count() # update scene, if scene switch is turned on (default) if self.sim_scene_switch == True: self.scene.disable_render = True self.func_update_scene() self.scene.disable_render = False # record wind & fila data, if record switch is turned on (default off) if self.sim_record_switch == True: # create a file containing wind data of this moment/step # name: wind_[step].mat sio.savemat(self.record_dir + '/wind_' + str(self.text_sim_step_count) + '.mat', \ {'wind_vector': self.sim_thread.wind_vector_field}) # create a file containing fila data of this moment/step # name: fila_[step].mat sio.savemat(self.record_dir + '/fila_' + str(self.text_sim_step_count) + '.mat', \ {'fila': self.sim_thread.plume_snapshot}) def _button_save_camera_angle_fired(self): cam = self.scene.mlab.view() Config.set_camera_view(cam) ################################### # Private functions def add_text_line(self, string): ''' Adds a line to Simulation State text box display ''' self.textbox_sim_state_display = (self.textbox_sim_state_display + string + "\n")[0:1000] # trigger scene update event def func_event_update_scene(self): self.event_need_update_scene = True # sim thread killed itself def func_sim_thread_killed_itself(self): # enable area size changing item (GUI) self.params_allow_change = True # update sim state indicator, tell communication process the sim will end self.objs_comm_process['sim_state'][0] = 0 def func_sim_step_count(self): # count sim step self.text_sim_step_count += 1 # count sim time self.text_sim_time_count = '%.2f s' % (self.text_sim_step_count * self.sim_dt) def func_wind_field_obj_init(self): # init wind vector field object, mayavi x, y, z = self.grid u, v, w = np.zeros_like(self.grid) self.wind_field = self.scene.mlab.quiver3d( x, y, z, u, v, w, reset_zoom=False, ) # update scene def func_update_scene(self): # update wind field display u, v, w = self.sim_thread.wind_vector_field self.wind_field.mlab_source.set(u=u, v=v, w=w) # update odor field display fila = self.sim_thread.plume_snapshot # update obj self.odor_field.mlab_source.reset(x=fila['x'], y=fila['y'], z=fila['z'], scalars=fila['r'] * 50) self.odor_field.mlab_source.set(x=fila['x']) # to trig display refresh # draw axes & outlines on current scene def func_init_axes_outline(self): # draw outline self.scene.mlab.outline(extent=[ 0, self.area_length, 0, self.area_width, 0, self.area_height ], ) # draw axes labels ax_label_x = 'West-East/X %.1f m' % (self.area_length) ax_label_y = 'North-South/Y %.1f m' % (self.area_width) ax_label_z = 'Up-Down/Z %.1f m' % (self.area_height) # determine text scale ax_text_scale = min(self.area_length, self.area_width, self.area_height) \ / float(max(len(ax_label_x), len(ax_label_y), len(ax_label_z))) self.scene.mlab.text3d(\ (self.area_length - ax_text_scale*len(ax_label_x))/2., \ -1.5*ax_text_scale, 0, ax_label_x, orient_to_camera=False, scale=ax_text_scale) self.scene.mlab.text3d(\ -.5*ax_text_scale, \ (self.area_width - ax_text_scale*len(ax_label_y))/2., \ 0, ax_label_y, orient_to_camera=False, scale=ax_text_scale, orientation=np.array([0,0,90])) self.scene.mlab.text3d(\ -1.*ax_text_scale, -1.*ax_text_scale, \ (self.area_height - ax_text_scale*len(ax_label_z))/2., \ ax_label_z, orient_to_camera=False, scale=ax_text_scale, orientation=np.array([0,270,315])) # update wind vane, the color and direction def func_change_wind_vane(self, mean_w_v): v = mean_w_v # paint color according to wind strength strength = (v[0]**2 + v[1]**2 + v[2]**2)**0.5 # get wind strength # convert strength to Red-Blue color map # 0 m/s: 0xFF0000 red # 10 m/s: 0x0000FF blue if strength > 10.: # 10 m/s is very strong strength = 10. RGB = int((0xFF0000 - 0x0000FF) / 10. * strength) self.wind_vane.color = (RGB / 0x10000 / 255., (RGB - RGB / 0x10000 * 0x10000 - RGB % 0x100) / 0x100 / 255., (RGB % 0x100) / 255.) # direction self.wind_vane.axis = (v[0], v[1], v[2]) # init wind vane drawing def func_init_wind_vane(self): # draw an arrow as a wind vane, at the original point self.wind_vane = visual.arrow(pos=(0, 0, 0)) self.func_change_wind_vane(self.mean_wind_vector) # init robot shape drawing def func_init_robot_shape(self, robot_pos): # draw 4 propellers d = 0.145 # diameter of propeller is 14.5 cm w = 0.16 # wheelbase is 16 cm (shortest distance between centers of two adjacent propellers) p1 = visual.ring(pos=(w/2.0, w/2.0, 0), axis = (0, 0, 1), \ radius = d/2.0, thickness = 0.01, color = (1.0, 0, 0)) # Red p2 = visual.ring(pos=(w/2.0, -w/2.0, 0), axis = (0, 0, 1), \ radius = d/2.0, thickness = 0.01, color = (0, 0, 1.0)) # Blue p3 = visual.ring(pos=(-w/2.0, -w/2.0, 0), axis = (0, 0, 1), \ radius = d/2.0, thickness = 0.01, color = (0, 0, 1.0)) # Blue p4 = visual.ring(pos=(-w/2.0, w/2.0, 0), axis = (0, 0, 1), \ radius = d/2.0, thickness = 0.01, color = (1.0, 0, 0)) # Red # robot shape is a frame composites different objects self.robot_draw = visual.frame(p1, p2, p3, p4) self.robot_draw.pos = (robot_pos[0], robot_pos[1], robot_pos[2]) # slice 3d mesh matrix to smaller matrix for quick display ''' input: interval ---- slice interval, must be even number interval/2:len(xyz_grids):interval ''' def func_slice_mesh_matrix_3d(self, mesh_grid, interval): x, y, z = mesh_grid x_s = x[interval / 2:x.shape[0]:interval, interval / 2:x.shape[1]:interval, interval / 2:x.shape[2]:interval] y_s = y[interval / 2:y.shape[0]:interval, interval / 2:y.shape[1]:interval, interval / 2:y.shape[2]:interval] z_s = z[interval / 2:z.shape[0]:interval, interval / 2:z.shape[1]:interval, interval / 2:z.shape[2]:interval] return x_s, y_s, z_s # slice 3d mesh matrix to x/y/z array, reverse process of mgrid def func_slice_mesh_matrix2array(self, mesh_grid, interval): x, y, z = mesh_grid x_s = x[interval / 2:x.shape[0]:interval, 0, 0] y_s = y[0, interval / 2:y.shape[1]:interval, 0] z_s = z[0, 0, interval / 2:z.shape[2]:interval] return x_s, y_s, z_s