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
Пример #2
0
 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
Пример #3
0
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)
Пример #5
0
 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
Пример #6
0
    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],
        )
Пример #7
0
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",
    )
Пример #8
0
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