Пример #1
0
class ObjectTree(QWidget, ComponentMixin):

    name = 'Object Tree'
    _stash = []

    preferences = Parameter.create(name='Preferences',
                                   children=[{
                                       'name': 'Preserve properties on reload',
                                       'type': 'bool',
                                       'value': False
                                   }, {
                                       'name': 'Clear all before each run',
                                       'type': 'bool',
                                       'value': True
                                   }, {
                                       'name': 'STL precision',
                                       'type': 'float',
                                       'value': .1
                                   }])

    sigObjectsAdded = pyqtSignal([list], [list, bool])
    sigObjectsRemoved = pyqtSignal(list)
    sigCQObjectSelected = pyqtSignal(object)
    sigAISObjectsSelected = pyqtSignal(list)
    sigItemChanged = pyqtSignal(QTreeWidgetItem, int)
    sigObjectPropertiesChanged = pyqtSignal()

    def __init__(self, parent):

        super(ObjectTree, self).__init__(parent)

        self.tree = tree = QTreeWidget(
            self, selectionMode=QAbstractItemView.ExtendedSelection)
        self.properties_editor = ParameterTree(self)

        tree.setHeaderHidden(True)
        tree.setItemsExpandable(False)
        tree.setRootIsDecorated(False)
        tree.setContextMenuPolicy(Qt.ActionsContextMenu)

        #forward itemChanged singal
        tree.itemChanged.connect(\
            lambda item,col: self.sigItemChanged.emit(item,col))
        #handle visibility changes form tree
        tree.itemChanged.connect(self.handleChecked)

        self.CQ = CQRootItem()
        self.Imports = ImportRootItem()
        self.Helpers = HelpersRootItem()

        root = tree.invisibleRootItem()
        root.addChild(self.CQ)
        root.addChild(self.Imports)
        root.addChild(self.Helpers)

        tree.expandToDepth(1)

        self._export_STL_action = \
            QAction('Export as STL',
                    self,
                    enabled=False,
                    triggered=lambda: \
                        self.export('stl',
                                    self.preferences['STL precision']))

        self._export_STEP_action = \
            QAction('Export as STEP',
                    self,
                    enabled=False,
                    triggered=lambda: \
                        self.export('step'))

        self._clear_current_action = QAction(icon('delete'),
                                             'Clear current',
                                             self,
                                             enabled=False,
                                             triggered=self.removeSelected)

        self._toolbar_actions = \
            [QAction(icon('delete-many'),'Clear all',self,triggered=self.removeObjects),
             self._clear_current_action,]

        self.prepareMenu()

        tree.itemSelectionChanged.connect(self.handleSelection)
        tree.customContextMenuRequested.connect(self.showMenu)

        self.prepareLayout()

    def prepareMenu(self):

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)

        self._context_menu = QMenu(self)
        self._context_menu.addActions(self._toolbar_actions)
        self._context_menu.addActions(
            (self._export_STL_action, self._export_STEP_action))

    def prepareLayout(self):

        self._splitter = splitter((self.tree, self.properties_editor),
                                  stretch_factors=(2, 1),
                                  orientation=Qt.Vertical)
        layout(self, (self._splitter, ), top_widget=self)

        self._splitter.show()

    def showMenu(self, position):

        self._context_menu.exec_(self.tree.viewport().mapToGlobal(position))

    def menuActions(self):

        return {'Tools': [self._export_STL_action, self._export_STEP_action]}

    def toolbarActions(self):

        return self._toolbar_actions

    def addLines(self):

        origin = (0, 0, 0)
        ais_list = []

        for name, color, direction in zip(('X', 'Y', 'Z'),
                                          ('red', 'lawngreen', 'blue'),
                                          ((1, 0, 0), (0, 1, 0), (0, 0, 1))):
            line_placement = Geom_Line(
                gp_Ax1(gp_Pnt(*origin), gp_Dir(*direction)))
            line = AIS_Line(line_placement)
            line.SetColor(to_occ_color(color))

            self.Helpers.addChild(ObjectTreeItem(name, ais=line))

            ais_list.append(line)

        self.sigObjectsAdded.emit(ais_list)

    def _current_properties(self):

        current_params = {}
        for i in range(self.CQ.childCount()):
            child = self.CQ.child(i)
            current_params[child.properties['Name']] = child.properties

        return current_params

    def _restore_properties(self, obj, properties):

        for p in properties[obj.properties['Name']]:
            obj.properties[p.name()] = p.value()

    @pyqtSlot(dict, bool)
    @pyqtSlot(dict)
    def addObjects(self, objects, clean=False, root=None):

        if root is None:
            root = self.CQ

        request_fit_view = True if root.childCount() == 0 else False
        preserve_props = self.preferences['Preserve properties on reload']

        if preserve_props:
            current_props = self._current_properties()

        if clean or self.preferences['Clear all before each run']:
            self.removeObjects()

        ais_list = []

        #remove empty objects
        objects_f = {
            k: v
            for k, v in objects.items() if not is_obj_empty(v.shape)
        }

        for name, obj in objects_f.items():
            ais, shape_display = make_AIS(obj.shape, obj.options)

            child = ObjectTreeItem(name,
                                   shape=obj.shape,
                                   shape_display=shape_display,
                                   ais=ais,
                                   sig=self.sigObjectPropertiesChanged)

            if preserve_props and name in current_props:
                self._restore_properties(child, current_props)

            if child.properties['Visible']:
                ais_list.append(ais)

            root.addChild(child)

        if request_fit_view:
            self.sigObjectsAdded[list, bool].emit(ais_list, True)
        else:
            self.sigObjectsAdded[list].emit(ais_list)

    @pyqtSlot(object, str, object)
    def addObject(self, obj, name='', options={}):

        root = self.CQ

        ais, shape_display = make_AIS(obj, options)

        root.addChild(
            ObjectTreeItem(name,
                           shape=obj,
                           shape_display=shape_display,
                           ais=ais,
                           sig=self.sigObjectPropertiesChanged))

        self.sigObjectsAdded.emit([ais])

    @pyqtSlot(list)
    @pyqtSlot()
    def removeObjects(self, objects=None):

        if objects:
            removed_items_ais = [self.CQ.takeChild(i).ais for i in objects]
        else:
            removed_items_ais = [ch.ais for ch in self.CQ.takeChildren()]

        self.sigObjectsRemoved.emit(removed_items_ais)

    @pyqtSlot(bool)
    def stashObjects(self, action: bool):

        if action:
            self._stash = self.CQ.takeChildren()
            removed_items_ais = [ch.ais for ch in self._stash]
            self.sigObjectsRemoved.emit(removed_items_ais)
        else:
            self.removeObjects()
            self.CQ.addChildren(self._stash)
            ais_list = [el.ais for el in self._stash]
            self.sigObjectsAdded.emit(ais_list)

    @pyqtSlot()
    def removeSelected(self):

        ixs = self.tree.selectedIndexes()
        rows = [ix.row() for ix in ixs]

        self.removeObjects(rows)

    def export(self, export_type, precision=None):

        items = self.tree.selectedItems()

        # if CQ models is selected get all children
        if [item for item in items if item is self.CQ]:
            CQ = self.CQ
            shapes = [CQ.child(i).shape for i in range(CQ.childCount())]
        # otherwise collect all selected children of CQ
        else:
            shapes = [item.shape for item in items if item.parent() is self.CQ]

        fname = get_save_filename(export_type)
        if fname is not '':
            export(shapes, export_type, fname, precision)

    @pyqtSlot()
    def handleSelection(self):

        items = self.tree.selectedItems()
        if len(items) == 0:
            self._export_STL_action.setEnabled(False)
            self._export_STEP_action.setEnabled(False)
            return

        # emit list of all selected ais objects (might be empty)
        ais_objects = [item.ais for item in items if item.parent() is self.CQ]
        self.sigAISObjectsSelected.emit(ais_objects)

        # handle context menu and emit last selected CQ  object (if present)
        item = items[-1]
        if item.parent() is self.CQ:
            self._export_STL_action.setEnabled(True)
            self._export_STEP_action.setEnabled(True)
            self._clear_current_action.setEnabled(True)
            self.sigCQObjectSelected.emit(item.shape)
            self.properties_editor.setParameters(item.properties,
                                                 showTop=False)
            self.properties_editor.setEnabled(True)
        elif item is self.CQ and item.childCount() > 0:
            self._export_STL_action.setEnabled(True)
            self._export_STEP_action.setEnabled(True)
        else:
            self._export_STL_action.setEnabled(False)
            self._export_STEP_action.setEnabled(False)
            self._clear_current_action.setEnabled(False)
            self.properties_editor.setEnabled(False)
            self.properties_editor.clear()

    @pyqtSlot(list)
    def handleGraphicalSelection(self, shapes):

        self.tree.clearSelection()

        CQ = self.CQ
        for i in range(CQ.childCount()):
            item = CQ.child(i)
            for shape in shapes:
                if item.shape_display.wrapped.IsEqual(shape):
                    item.setSelected(True)

    @pyqtSlot(QTreeWidgetItem, int)
    def handleChecked(self, item, col):

        if type(item) is ObjectTreeItem:
            if item.checkState(0):
                item.properties['Visible'] = True
            else:
                item.properties['Visible'] = False
Пример #2
0
class BeeActions(QObject):
    log_signal = pyqtSignal(str)

    def __init__(self, dockarea):
        super().__init__()
        self.dockarea = dockarea
        self.dockarea.dock_signal.connect(self.save_layout_state_auto)
        self.mainwindow = dockarea.parent()
        self.chrono = ChronoTimer(dockarea)
        self.author = 'Aurore Avargues'
        self.h5saver = H5Saver()
        self.h5saver.new_file_sig.connect(self.create_new_file)
        self.settings = None
        self.shortcut_file = None
        self.shortcuts = []
        self.shortcut_manager = ShortCutManager(list_actions)
        self.timestamp_array = None
        self.action_array = None
        self.bee_array = None
        self.setup_ui()

    def setup_ui(self):
        #creating the menubar
        self.menubar = self.mainwindow.menuBar()
        self.create_menu(self.menubar)

        #disconnect normal chrono/timer behaviour with the start button
        self.chrono.start_pb.disconnect()
        self.chrono.start_pb.clicked.connect(self.set_scan)
        self.chrono.reset_pb.clicked.connect(self.stop_daq)

        self.settings_dock = Dock('Settings')
        self.dockarea.addDock(self.settings_dock, 'bottom',
                              self.chrono.dock_controls)

        self.dock_daq = Dock('Data Acquisition')
        self.dockarea.addDock(self.dock_daq, 'right')
        self.logger_list = QtWidgets.QListWidget()
        self.logger_list.setMinimumWidth(300)
        self.dock_daq.addWidget(self.logger_list)

        self.init_tree = ParameterTree()
        self.init_tree.setMinimumWidth(300)
        self.init_tree.setMinimumHeight(150)
        self.settings_dock.addWidget(self.init_tree)
        self.settings_dock.addWidget(self.h5saver.settings_tree)
        self.h5saver.settings.child(('save_type')).hide()
        self.h5saver.settings.child(('save_2D')).hide()
        self.h5saver.settings.child(('save_raw_only')).hide()
        self.h5saver.settings.child(('do_save')).hide()
        self.h5saver.settings.child(('custom_name')).hide()

        self.settings = Parameter.create(name='init_settings',
                                         type='group',
                                         children=[
                                             {
                                                 'title':
                                                 'Loaded presets',
                                                 'name':
                                                 'loaded_files',
                                                 'type':
                                                 'group',
                                                 'children': [
                                                     {
                                                         'title':
                                                         'Shortcut file',
                                                         'name':
                                                         'shortcut_file',
                                                         'type': 'str',
                                                         'value': '',
                                                         'readonly': True
                                                     },
                                                     {
                                                         'title':
                                                         'Layout file',
                                                         'name': 'layout_file',
                                                         'type': 'str',
                                                         'value': '',
                                                         'readonly': True
                                                     },
                                                 ]
                                             },
                                             {
                                                 'title':
                                                 'Settings',
                                                 'name':
                                                 'settings',
                                                 'type':
                                                 'group',
                                                 'children': [
                                                     {
                                                         'title':
                                                         'Save Bee number',
                                                         'name':
                                                         'save_bee_number',
                                                         'type': 'bool',
                                                         'value': True
                                                     },
                                                 ]
                                             },
                                             {
                                                 'title': 'Shortcuts',
                                                 'name': 'shortcuts',
                                                 'type': 'group',
                                                 'children': []
                                             },
                                         ])
        self.init_tree.setParameters(self.settings, showTop=False)
        self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)

        #params about dataset attributes and scan attibutes
        date = QDateTime(QDate.currentDate(), QTime.currentTime())
        params_dataset = [{
            'title':
            'Dataset information',
            'name':
            'dataset_info',
            'type':
            'group',
            'children': [{
                'title': 'Author:',
                'name': 'author',
                'type': 'str',
                'value': self.author
            }, {
                'title': 'Date/time:',
                'name': 'date_time',
                'type': 'date_time',
                'value': date
            }, {
                'title': 'Sample:',
                'name': 'sample',
                'type': 'str',
                'value': ''
            }, {
                'title': 'Experiment type:',
                'name': 'experiment_type',
                'type': 'str',
                'value': ''
            }, {
                'title': 'Description:',
                'name': 'description',
                'type': 'text',
                'value': ''
            }]
        }]

        params_scan = [{
            'title':
            'Scan information',
            'name':
            'scan_info',
            'type':
            'group',
            'children': [
                {
                    'title': 'Author:',
                    'name': 'author',
                    'type': 'str',
                    'value': self.author
                },
                {
                    'title': 'Date/time:',
                    'name': 'date_time',
                    'type': 'date_time',
                    'value': date
                },
                {
                    'title': 'Scan type:',
                    'name': 'scan_type',
                    'type': 'list',
                    'value': 'Scan1D',
                    'values': ['Scan1D', 'Scan2D']
                },
                {
                    'title': 'Scan name:',
                    'name': 'scan_name',
                    'type': 'str',
                    'value': '',
                    'readonly': True
                },
                {
                    'title': 'Description:',
                    'name': 'description',
                    'type': 'text',
                    'value': ''
                },
            ]
        }]

        self.dataset_attributes = Parameter.create(name='Attributes',
                                                   type='group',
                                                   children=params_dataset)
        self.scan_attributes = Parameter.create(name='Attributes',
                                                type='group',
                                                children=params_scan)

    def parameter_tree_changed(self, param, changes):
        """
            | Check eventual changes in the changes list parameter.
            |
            | In case of changed values, emit the signal containing the current path and parameter via update_settings_signal to the connected hardware.

            =============== ====================================    ==================================================
            **Parameters**   **Type**                                **Description**

             *param*         instance of pyqtgraph parameter         The parameter to be checked

             *changes*       (parameter,change,infos) tuple list     The (parameter,change,infos) list to be treated
            =============== ====================================    ==================================================
        """

        for param, change, data in changes:
            path = self.settings.childPath(param)
            if path is not None:
                childName = '.'.join(path)
            else:
                childName = param.name()
            if change == 'childAdded':
                pass
            elif change == 'value':
                if param.name() in custom_tree.iter_children(
                        self.settings.child(('shortcuts')), []):
                    if param.parent().name() == 'shortcuts':
                        param_index = custom_tree.iter_children(
                            self.settings.child(('shortcuts')),
                            []).index(param.name())
                        action = self.shortcut_manager.shortcut_params.child(
                            ('actions')).children()[param_index].child(
                                ('action')).value()
                        self.activate_shortcut(self.shortcuts[param_index],
                                               action,
                                               activate=param.value())

            elif change == 'parent':
                pass

    def activate_shortcut(self, shortcut, action='', activate=True):
        if activate:
            shortcut.activated.connect(self.create_activated_slot(action))
        else:
            try:
                shortcut.activated.disconnect()
            except:
                pass

    def create_activated_slot(self, action):
        return lambda: self.log_data(action)

    def log_data(self, action=''):
        now = self.chrono.get_elapsed_time()
        if self.settings.child('settings', 'save_bee_number').value():
            widget = QtWidgets.QWidget()
            index, res = QtWidgets.QInputDialog.getInt(
                widget, 'Bee number', 'Pick a number for this bee!')
            if res:
                new_item = QtWidgets.QListWidgetItem(
                    f'Elapsed time: {int(now)} s, Bee {index} did :{action}')
                self.logger_list.insertItem(0, new_item)

                self.h5saver.append(self.action_array, action)
                self.h5saver.append(self.timestamp_array, np.array([now]))
                self.h5saver.append(self.bee_array, np.array([index]))
        else:
            new_item = QtWidgets.QListWidgetItem(
                f'Elapsed time: {int(now)} s:{action}')
            self.logger_list.insertItem(0, new_item)
            self.h5saver.append(self.action_array, action)
            self.h5saver.append(self.timestamp_array, np.array([now]))
        self.h5saver.flush()

    def create_shortcuts(self):
        pass

    def create_new_file(self, new_file):
        self.h5saver.init_file(update_h5=new_file)
        res = self.update_file_settings(new_file)
        return res

    def set_scan(self):
        """
        Sets the current scan given the selected settings. Makes some checks, increments the h5 file scans.
        In case the dialog is cancelled, return False and aborts the scan
        """
        try:
            if self.shortcut_file is not None:

                # set the filename and path
                res = self.create_new_file(False)
                if not res:
                    return

                #create the arrays within the current scan group
                self.timestamp_array = self.h5saver.add_array(
                    self.h5saver.current_scan_group,
                    'time_axis',
                    'data',
                    scan_type='scan1D',
                    enlargeable=True,
                    array_to_save=np.array([
                        0.,
                    ]),
                    data_shape=(1, ),
                    title='Timestamps',
                    metadata=dict(units='seconds'))

                self.action_array = self.h5saver.add_string_array(
                    self.h5saver.current_scan_group,
                    'actions',
                    title='Actions',
                    metadata=dict([]))
                if self.settings.child('settings', 'save_bee_number').value():
                    self.bee_array = self.h5saver.add_array(
                        self.h5saver.current_scan_group,
                        'bees',
                        'data',
                        scan_type='scan1D',
                        enlargeable=True,
                        array_to_save=np.array([
                            0,
                        ]),
                        title='Bees',
                        data_shape=(1, ))

                current_filename = self.h5saver.settings.child(
                    ('current_scan_name')).value()
                self.init_tree.setEnabled(False)
                self.h5saver.settings_tree.setEnabled(False)
                self.logger_list.clear()

                self.h5saver.current_scan_group._v_attrs['scan_done'] = False
                # if all metadat steps have been validated, start the chrono
                self.chrono.start()

                return True
            else:
                mssg = QtWidgets.QMessageBox()
                mssg.setText(
                    'You have to load a shortcut file configuration before starting'
                )
                mssg.exec()
                return False

        except Exception as e:
            self.update_status(getLineInfo() + str(e))

    def stop_daq(self):
        self.h5saver.current_scan_group._v_attrs['scan_done'] = True
        self.init_tree.setEnabled(True)
        self.h5saver.settings_tree.setEnabled(True)
        self.h5saver.flush()

    def update_file_settings(self, new_file=False):
        try:
            if self.h5saver.current_scan_group is None:
                new_file = True

            if new_file:
                self.set_metadata_about_dataset()
                self.save_metadata(self.h5saver.raw_group, 'dataset_info')

            if self.h5saver.current_scan_name is None:
                self.h5saver.add_scan_group()
            elif not self.h5saver.is_node_in_group(
                    self.h5saver.raw_group, self.h5saver.current_scan_name):
                self.h5saver.add_scan_group()

            #set attributes to the current group, such as scan_type....
            self.scan_attributes.child('scan_info',
                                       'scan_type').setValue('Scan1D')
            self.scan_attributes.child('scan_info', 'scan_name').setValue(
                self.h5saver.current_scan_group._v_name)
            self.scan_attributes.child('scan_info', 'description').setValue(
                self.h5saver.current_scan_group._v_attrs['description'])
            res = self.set_metadata_about_current_scan()
            self.save_metadata(self.h5saver.current_scan_group, 'scan_info')
            return res

        except Exception as e:
            self.update_status(getLineInfo() + str(e))

    def set_metadata_about_current_scan(self):
        """
            Set the date/time and author values of the scan_info child of the scan_attributes tree.
            Show the 'scan' file attributes.

            See Also
            --------
            show_file_attributes
        """
        date = QDateTime(QDate.currentDate(), QTime.currentTime())
        self.scan_attributes.child('scan_info', 'date_time').setValue(date)
        self.scan_attributes.child('scan_info', 'author').setValue(
            self.dataset_attributes.child('dataset_info', 'author').value())
        res = self.show_file_attributes('scan')
        return res

    def set_metadata_about_dataset(self):
        """
            Set the date value of the data_set_info-date_time child of the data_set_attributes tree.
            Show the 'dataset' file attributes.

            See Also
            --------
            show_file_attributes
        """
        date = QDateTime(QDate.currentDate(), QTime.currentTime())
        self.dataset_attributes.child('dataset_info',
                                      'date_time').setValue(date)
        res = self.show_file_attributes('dataset')
        return res

    def show_file_attributes(self, type_info='dataset'):
        """
        """
        dialog = QtWidgets.QDialog()
        vlayout = QtWidgets.QVBoxLayout()
        tree = ParameterTree()
        tree.setMinimumWidth(400)
        tree.setMinimumHeight(500)
        if type_info == 'scan':
            tree.setParameters(self.scan_attributes, showTop=False)
        elif type_info == 'dataset':
            tree.setParameters(self.dataset_attributes, showTop=False)

        vlayout.addWidget(tree)
        dialog.setLayout(vlayout)
        buttonBox = QtWidgets.QDialogButtonBox(parent=dialog)
        buttonBox.addButton('Cancel', buttonBox.RejectRole)
        buttonBox.addButton('Apply', buttonBox.AcceptRole)
        buttonBox.rejected.connect(dialog.reject)
        buttonBox.accepted.connect(dialog.accept)

        vlayout.addWidget(buttonBox)
        dialog.setWindowTitle(
            'Fill in information about this {}'.format(type_info))
        res = dialog.exec()
        return res

    def save_metadata(self, node, type_info='dataset_info'):
        """
        """

        attr = node._v_attrs
        if type_info == 'dataset_info':
            attr['type'] = 'dataset'
            params = self.dataset_attributes
        else:
            attr['type'] = 'scan'
            params = self.scan_attributes
        for child in params.child((type_info)).children():
            if isinstance(child.value(), QDateTime):
                attr[child.name()] = child.value().toString(
                    'dd/mm/yyyy HH:MM:ss')
            else:
                attr[child.name()] = child.value()
        if type_info == 'dataset_info':
            #save contents of given parameter object into an xml string under the attribute settings
            settings_str = b'<All_settings title="All Settings" type="group">'
            settings_str += custom_tree.parameter_to_xml_string(params)
            settings_str += custom_tree.parameter_to_xml_string(self.settings)
            if hasattr(self.shortcut_manager, 'shortcut_params'):
                settings_str += custom_tree.parameter_to_xml_string(
                    self.shortcut_manager.shortcut_params)
            settings_str += b'</All_settings>'

            attr.settings = settings_str

        elif type_info == 'scan_info':
            settings_str = b'<All_settings title="All Settings" type="group">' + \
                           custom_tree.parameter_to_xml_string(params) + \
                           custom_tree.parameter_to_xml_string(self.settings) + \
                           custom_tree.parameter_to_xml_string(self.h5saver.settings) + b'</All_settings>'

            attr.settings = settings_str

    def show_log(self):
        import webbrowser
        webbrowser.open(logging.getLoggerClass().root.handlers[0].baseFilename)

    def show_file(self):
        self.h5saver.flush()
        self.h5saver.show_file_content()

    def create_menu(self, menubar):
        menubar.clear()

        # %% create Settings menu
        self.file_menu = menubar.addMenu('File')
        self.file_menu.addAction('Show log file', self.show_log)
        self.file_menu.addAction('Show data file', self.show_file)

        self.file_menu.addSeparator()
        quit_action = self.file_menu.addAction('Quit')
        quit_action.triggered.connect(self.quit_fun)

        self.settings_menu = menubar.addMenu('Settings')
        docked_menu = self.settings_menu.addMenu('Docked windows')
        action_load = docked_menu.addAction('Load Layout')
        action_save = docked_menu.addAction('Save Layout')

        action_load.triggered.connect(self.load_layout_state)
        action_save.triggered.connect(self.save_layout_state)
        docked_menu.addSeparator()

        self.preset_menu = menubar.addMenu('Preset Shortcuts')
        action_new_preset = self.preset_menu.addAction('New preset')
        # action.triggered.connect(lambda: self.show_file_attributes(type_info='preset'))
        action_new_preset.triggered.connect(self.create_preset)
        action_modify_preset = self.preset_menu.addAction('Modify preset')
        action_modify_preset.triggered.connect(self.modify_shortcuts)
        self.preset_menu.addSeparator()
        load_preset = self.preset_menu.addMenu('Load presets')

        slots = dict([])
        for ind_file, file in enumerate(os.listdir(shortcut_path)):
            if file.endswith(".xml"):
                (filesplited, ext) = os.path.splitext(file)
                slots[filesplited] = load_preset.addAction(filesplited)
                slots[filesplited].triggered.connect(
                    self.create_menu_slot(os.path.join(shortcut_path, file)))

    def modify_shortcuts(self):
        try:
            path = select_file(start_path=shortcut_path, save=False, ext='xml')
            if path != '':
                self.shortcut_manager.set_file_preset(str(path))

                mssg = QtWidgets.QMessageBox()
                mssg.setText(
                    f'You have to restart the application to take the modifications into account! '
                    f'Quitting the application...')
                mssg.exec()
                self.quit_fun()
            else:  # cancel
                pass
        except Exception as e:
            self.update_status(getLineInfo() + str(e))

    def create_menu_slot(self, filename):
        return lambda: self.set_shortcut_mode(filename)

    def set_shortcut_mode(self, filename):
        #TODO: apply shortcuts to this widget
        tail, fileext = os.path.split(filename)
        file, ext = os.path.splitext(fileext)
        if ext == '.xml':
            self.shortcut_file = filename
            self.shortcut_manager.set_file_preset(filename, show=False)
            self.settings.child('loaded_files',
                                'shortcut_file').setValue(filename)
            self.author = self.shortcut_manager.shortcut_params.child(
                ('author')).value()
            self.dataset_attributes.child('dataset_info',
                                          'author').setValue(self.author)
            self.scan_attributes.child('scan_info',
                                       'author').setValue(self.author)

            path = os.path.join(layout_path, file + '.dock')
            if os.path.isfile(path):
                self.load_layout_state(path)

            #remove existing shorcuts
            while len(self.shortcuts):
                self.shortcuts.pop(0)

            for ind, shortcut in enumerate(
                    self.shortcut_manager.shortcut_params.child(
                        ('actions')).children()):
                stc = QtWidgets.QShortcut(
                    QtGui.QKeySequence(shortcut.child(('shortcut')).value()),
                    self.dockarea)
                self.settings.child(('shortcuts')).addChild({
                    'title':
                    f"Shortcut{ind:02d}: {shortcut.child(('action')).value()} {shortcut.child(('shortcut')).value()}:",
                    'name': f'shortcut{ind:02d}',
                    'type': 'led_push',
                    'value': True
                })

                self.shortcuts.append(stc)
                self.activate_shortcut(stc,
                                       shortcut.child(('action')).value(),
                                       activate=True)

    def create_preset(self):
        try:
            self.shortcut_manager.set_new_preset()
            self.create_menu(self.menubar)
        except Exception as e:
            self.update_status(getLineInfo() + str(e))

    def save_layout_state(self, file=None):
        """
            Save the current layout state in the select_file obtained pathname file.
            Once done dump the pickle.

            See Also
            --------
            utils.select_file
        """
        try:
            dockstate = self.dockarea.saveState()
            if file is None:
                file = select_file(start_path=None, save=True, ext='dock')
            if file is not None:
                with open(str(file), 'wb') as f:
                    pickle.dump(dockstate, f, pickle.HIGHEST_PROTOCOL)
        except:
            pass

    def save_layout_state_auto(self):
        if self.shortcut_file is not None:
            file = os.path.split(self.shortcut_file)[1]
            file = os.path.splitext(file)[0]
            path = os.path.join(layout_path, file + '.dock')
            self.save_layout_state(path)

    def load_layout_state(self, file=None):
        """
            Load and restore a layout state from the select_file obtained pathname file.

            See Also
            --------
            utils.select_file
        """
        try:
            if file is None:
                file = select_file(save=False, ext='dock')
            if file is not None:
                with open(str(file), 'rb') as f:
                    dockstate = pickle.load(f)
                    self.dockarea.restoreState(dockstate)
            file = os.path.split(file)[1]
            self.settings.child('loaded_files', 'layout_file').setValue(file)
        except:
            pass

    def quit_fun(self):
        """
            Quit the current instance of DAQ_scan and close on cascade move and detector modules.

            See Also
            --------
            quit_fun
        """
        try:
            areas = self.dockarea.tempAreas[:]
            for area in areas:
                area.win.close()
                QtWidgets.QApplication.processEvents()
                QThread.msleep(1000)
                QtWidgets.QApplication.processEvents()

            if hasattr(self, 'mainwindow'):
                self.mainwindow.close()

        except Exception as e:
            pass

    def update_status(self, txt):
        """
            Show the txt message in the status bar with a delay of wait_time ms.

            =============== =========== =======================
            **Parameters**    **Type**    **Description**
            *txt*             string      The message to show
            *wait_time*       int         the delay of showing
            *log_type*        string      the type of the log
            =============== =========== =======================
        """
        try:
            self.log_signal.emit(txt)
            logging.info(txt)

        except Exception as e:
            pass
Пример #3
0
class liveSpecWrangler(wranglerWidget):
    """Widget for controlling spec and reading in data as it is being
    collected. 
    
    attributes:
        fname: str, file path for data storage
        file_lock: multiprocessing Condition, multiprocess safe lock
            to ensure only one process accesses data file at a time.
        ui: Qt Ui_Form, holds all gui widgets made with qtdesigner
        specCommandLine: commandLine, widget to simulate terminal line
        commands: list, set of previously entered commands
        command_queue: Queue, used to send commands to thread
        current: int, index of current command
        file_lock, mp.Condition, process safe lock for file access
        fname: str, path to data file
        keep_trying: bool, unused
        parameters: pyqtgraph Parameter, set of parameters for
            controling widget
        scan_name: str, current scan name, used to handle syncing data
        sphere_args: dict, used as **kwargs in sphere initialization.
            see EwaldSphere.
        tree: pyqtgraph ParameterTree, tree which holds parameters
        thread: liveSpecThread, thread which controls processes to
            watch for new data and to run integration
    
    methods:
        enabled: enables or disables all gui elements.
        send_command: sends command through to spec via the bServer
        set_fname: Method to safely change file name
        set_image_dir: sets the image directory
        set_out_dir: sets the output directory for new data files
        set_pdi_dir: sets the pdi directory
        set_poni_file: sets the calibration poni file
        setup: sets up the thread attribute to ensure all parameters
            are properly synced.
        stop_watching: end the watcher process.
        update_file: update the current scan name and file path 
            attributes
    
    signals:
        finished: Should be connected to thread.finished signal
        showLabel: str, text to be set as specLabel.
        sigStart: Tells staticWidget to start the thread and prepare
            for new data.
        sigUpdateData: int, signals a new arch has been added.
        sigUpdateFile: (str, str), sends new scan_name and file name
            to staticWidget.
    """
    showLabel = Qt.QtCore.Signal(str)

    def __init__(self, fname, file_lock, parent=None):
        """fname: str, path to data file. 
        file_lock: Condition, process safe lock.
        """
        super().__init__(fname, file_lock, parent)

        # Setup gui elements
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.ui.startButton.clicked.connect(self.sigStart.emit)
        self.ui.stopButton.clicked.connect(self.stop_watching)
        self.specCommandLine = commandLine(self)
        self.specCommandLine.send_command = self.send_command
        self.ui.commandLayout.addWidget(self.specCommandLine)
        self.buttonSend = QtWidgets.QPushButton(self)
        self.buttonSend.setText('Send')
        self.buttonSend.clicked.connect(self.send_command)
        self.ui.commandLayout.addWidget(self.buttonSend)
        self.commands = ['']
        self.current = -1
        self.keep_trying = True
        self.showLabel.connect(self.ui.specLabel.setText)

        # Setup the parameter tree
        self.tree = ParameterTree()
        self.parameters = Parameter.create(name='live_spec_wrangler',
                                           type='group',
                                           children=params)
        self.tree.setParameters(self.parameters, showTop=False)
        self.layout = Qt.QtWidgets.QVBoxLayout(self.ui.paramFrame)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self.tree)
        self.parameters.child('image_dir_browse').sigActivated.connect(
            self.set_image_dir)
        self.parameters.child('pdi_dir_browse').sigActivated.connect(
            self.set_pdi_dir)
        self.parameters.child('poni_file_browse').sigActivated.connect(
            self.set_poni_file)
        self.parameters.child('out_dir_browse').sigActivated.connect(
            self.set_out_dir)
        self.parameters.sigTreeStateChanged.connect(self.update)

        # Setup the liveSpecThread
        self.thread = liveSpecThread(
            command_queue=self.command_queue,
            sphere_args=self.sphere_args,
            fname=self.fname,
            file_lock=self.file_lock,
            mp_inputs=self._get_mp_inputs(),
            img_dir=self.parameters.child('Image Directory').value(),
            pdi_dir=self.parameters.child('PDI Directory').value(),
            out_dir=self.parameters.child('Out Directory').value(),
            filetypes=self.parameters.child('File Types').value().split(),
            pollingperiod=self.parameters.child('Polling Period').value(),
            parent=self)
        self.thread.showLabel.connect(self.ui.specLabel.setText)
        self.thread.sigUpdateFile.connect(self.update_file)
        self.thread.finished.connect(self.finished.emit)
        self.thread.sigUpdate.connect(self.sigUpdateData.emit)
        self.setup()

    def setup(self):
        """Syncs all attributes of liveSpecThread with parameters
        """
        self.thread.sphere_args.update(self.sphere_args)
        self.thread.fname = self.fname
        self.thread.mp_inputs.update(self._get_mp_inputs())
        self.thread.img_dir = self.parameters.child('Image Directory').value()
        self.thread.pdi_dir = self.parameters.child('PDI Directory').value()
        self.thread.out_dir = self.parameters.child('Out Directory').value()
        self.thread.filetypes = self.parameters.child(
            'File Types').value().split()
        self.thread.set_queues()
        self.thread.pollingperiod = self.parameters.child(
            'Polling Period').value()

    def send_command(self):
        """Sends command in command line to spec, and calls
        commandLine send_command method to add command to list of
        commands.
        """
        command = self.specCommandLine.text()
        if not (command.isspace() or command == ''):
            try:
                specCommand(command, queue=True)
            except Exception as e:
                print(e)
                print(f"Command '{command}' not sent")

        commandLine.send_command(self.specCommandLine)

    def set_image_dir(self):
        """Opens file dialogue and sets the image directory
        """
        dname = Qt.QtWidgets.QFileDialog.getExistingDirectory(self)
        if dname != '':
            self.parameters.child('Image Directory').setValue(dname)

    def set_pdi_dir(self):
        """Opens file dialogue and sets the pdi directory
        """
        dname = Qt.QtWidgets.QFileDialog.getExistingDirectory(self)
        if dname != '':
            self.parameters.child('PDI Directory').setValue(dname)

    def set_out_dir(self):
        """Opens file dialogue and sets the output directory
        """
        dname = Qt.QtWidgets.QFileDialog.getExistingDirectory(self)
        if dname != '':
            self.parameters.child('Out Directory').setValue(dname)

    def set_poni_file(self):
        """Opens file dialogue and sets the calibration file
        """
        fname, _ = Qt.QtWidgets.QFileDialog().getOpenFileName()
        if fname != '':
            self.parameters.child('Calibration PONI File').setValue(fname)

    def _get_mp_inputs(self):
        """Organizes inputs for MakePONI from parameters.
        """
        mp_inputs = OrderedDict(rotations={
            "rot1": None,
            "rot2": None,
            "rot3": None
        },
                                calib_rotations={
                                    "rot1": 0,
                                    "rot2": 0,
                                    "rot3": 0
                                },
                                poni_file=None,
                                spec_dict={})
        rot_mot = self.parameters.child('Rotation Motors')
        for child in rot_mot:
            if child.value() == "":
                mp_inputs['rotations'][child.name().lower()] = None
            else:
                mp_inputs['rotations'][child.name().lower()] = child.value()

        cal_rot = self.parameters.child('Calibration Angles')
        for child in cal_rot:
            if child.value() == 0:
                pass
            else:
                mp_inputs['calib_rotations'][
                    child.name().lower()] = child.value()

        mp_inputs['poni_file'] = self.parameters.child(
            'Calibration PONI File').value()

        return mp_inputs

    def update_file(self, name, fname):
        """updates the current scan name and file path attributes, emits
        them back to main widget.
        
        args:
            name: str, scan name
            fname: str, path to data file
        """
        self.scan_name = name
        self.fname = fname
        self.sigUpdateFile.emit(name, fname)

    def enabled(self, enable):
        """Sets tree and start button to enable.
        
        args:
            enable: bool, True for enabled False for disabled.
        """
        self.tree.setEnabled(enable)
        self.ui.startButton.setEnabled(enable)

    def stop_watching(self):
        """Sends stop command to thread.
        """
        self.command_queue.put('stop')
Пример #4
0
class specWrangler(wranglerWidget):
    """Widget for integrating data associated with spec file. Can be
    used "live", will continue to poll data folders until image data
    and corresponding spec data are available.
    
    attributes:
        command_queue: Queue, used to send commands to thread
        file_lock, mp.Condition, process safe lock for file access
        fname: str, path to data file
        parameters: pyqtgraph Parameter, stores parameters from user
        scan_name: str, current scan name, used to handle syncing data
        sphere_args: dict, used as **kwargs in sphere initialization.
            see EwaldSphere.
        thread: wranglerThread or subclass, QThread for controlling
            processes
        timeout: int, how long before thread stops looking for new
            data.
        tree: pyqtgraph ParameterTree, stores and organizes parameters
        ui: Ui_Form from qtdesigner
    
    methods:
        cont, pause, stop: functions to pass continue, pause, and stop
            commands to thread via command_queue 
        enabled: Enables or disables interactivity
        set_image_dir: sets the image directory
        set_poni_file: sets the calibration poni file
        set_spec_file: sets the spec data file
        set_fname: Method to safely change file name
        setup: Syncs thread parameters prior to starting
    
    signals:
        finished: Connected to thread.finished signal
        sigStart: Tells tthetaWidget to start the thread and prepare
            for new data.
        sigUpdateData: int, signals a new arch has been added.
        sigUpdateFile: (str, str), sends new scan_name and file name
            to tthetaWidget.
        showLabel: str, connected to thread showLabel signal, sets text
            in specLabel
    """
    showLabel = Qt.QtCore.Signal(str)

    def __init__(self, fname, file_lock, parent=None):
        """fname: str, file path
        file_lock: mp.Condition, process safe lock
        """
        # ic()
        super().__init__(fname, file_lock, parent)

        # Setup gui elements
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.ui.startButton.clicked.connect(self.sigStart.emit)
        self.ui.pauseButton.clicked.connect(self.pause)
        self.ui.stopButton.clicked.connect(self.stop)
        self.ui.continueButton.clicked.connect(self.cont)

        # Setup parameter tree
        self.tree = ParameterTree()
        self.parameters = Parameter.create(name='spec_wrangler',
                                           type='group',
                                           children=params)
        self.tree.setParameters(self.parameters, showTop=False)
        self.layout = Qt.QtWidgets.QVBoxLayout(self.ui.paramFrame)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self.tree)

        # Wire signals from parameter tree based buttons
        self.parameters.child('spec_file_browse').sigActivated.connect(
            self.set_spec_file)
        self.parameters.child('image_dir_browse').sigActivated.connect(
            self.set_image_dir)
        self.parameters.child('poni_file_browse').sigActivated.connect(
            self.set_poni_file)

        # Set attributes
        self.scan_number = self.parameters.child('Scan Number').value()
        self.timeout = self.parameters.child('Timeout').value()
        self.parameters.sigTreeStateChanged.connect(self.setup)

        # Setup thread
        self.thread = specThread(self.command_queue, self.sphere_args,
                                 self.fname, self.file_lock, self.scan_name, 0,
                                 {}, {}, None, 5, self)
        self.thread.showLabel.connect(self.ui.specLabel.setText)
        self.thread.sigUpdateFile.connect(self.sigUpdateFile.emit)
        self.thread.finished.connect(self.finished.emit)
        self.thread.sigUpdate.connect(self.sigUpdateData.emit)

        self.mask = None
        self.mask_widget = MaskWidget()
        key = self.parameters.child("Detector").value()
        data = np.zeros(DETECTOR_DICT[key]["shape"])
        data[0, 0] = 1
        self.mask_widget.set_data(data.T)
        self.mask_widget.hide()
        self.parameters.child('set_mask').sigActivated.connect(
            self.launch_mask_widget)

        self.mask_widget.newMask.connect(self.set_mask)

        self.setup()

    def setup(self):
        """Sets up the child thread, syncs all parameters.
        """
        # ic()
        self.thread.mp_inputs.update(self._get_mp_inputs())
        lsf_inputs = self._get_lsf_inputs()
        self.thread.lsf_inputs.update(lsf_inputs)
        self.scan_number = self.parameters.child('Scan Number').value()
        self.scan_name = lsf_inputs['spec_file_name'] + '_scan' + \
                         str(self.scan_number)
        self.fname = os.path.join(lsf_inputs['spec_file_path'],
                                  self.scan_name + '.hdf5')
        self.thread.fname = self.fname
        self.thread.scan_name = self.scan_name
        self.thread.scan_number = self.scan_number
        self.thread.img_dir = self.parameters.child('Image Directory').value()
        self.timeout = self.parameters.child('Timeout').value()
        self.thread.timeout = self.parameters.child('Timeout').value()
        self.thread.file_lock = self.file_lock
        self.thread.sphere_args = self.sphere_args
        self.thread.mask = self.mask

        if self.parameters.child('Rotation Motors').child(
                'Rot2').value() == '':
            self.parameters.child('Rotation Motors').child('Rot2').setValue(
                'tth')

    def pause(self):
        if self.thread.isRunning():
            self.command_queue.put('pause')

    def cont(self):
        if self.thread.isRunning():
            self.command_queue.put('continue')

    def stop(self):
        if self.thread.isRunning():
            self.command_queue.put('stop')

    def set_spec_file(self):
        """Opens file dialogue and sets the spec data file
        """
        # ic()
        fname, _ = Qt.QtWidgets.QFileDialog().getOpenFileName()
        if fname != '':
            self.parameters.child('Spec File').setValue(fname)

    def set_image_dir(self):
        """Opens file dialogue and sets the image directory
        """
        # ic()
        dname = Qt.QtWidgets.QFileDialog.getExistingDirectory(self)
        if dname != '':
            self.parameters.child('Image Directory').setValue(dname)

    def set_poni_file(self):
        """Opens file dialogue and sets the calibration file
        """
        # ic()
        fname, _ = Qt.QtWidgets.QFileDialog().getOpenFileName()
        if fname != '':
            self.parameters.child('Calibration PONI File').setValue(fname)

    def _get_mp_inputs(self):
        """Organizes inputs for MakePONI from parameters.
        """
        # ic()
        mp_inputs = OrderedDict(rotations={
            "rot1": None,
            "rot2": 'tth',
            "rot3": None
        },
                                calib_rotations={
                                    "rot1": 0,
                                    "rot2": 0,
                                    "rot3": 0
                                },
                                poni_file=None,
                                spec_dict={})
        rot_mot = self.parameters.child('Rotation Motors')
        for child in rot_mot:
            if child.value() == "":
                mp_inputs['rotations'][child.name().lower()] = None
            else:
                mp_inputs['rotations'][child.name().lower()] = child.value()

        cal_rot = self.parameters.child('Calibration Angles')
        for child in cal_rot:
            if child.value() == 0:
                pass
            else:
                mp_inputs['calib_rotations'][
                    child.name().lower()] = child.value()

        mp_inputs['poni_file'] = self.parameters.child(
            'Calibration PONI File').value()

        return mp_inputs

    def _get_lsf_inputs(self):
        """Organizes inputs for LoadSpecFile from parameters. No longer
        used.
        """
        # ic()
        dirname, fname = os.path.split(
            self.parameters.child('Spec File').value())
        lsf_inputs = OrderedDict(spec_file_path=dirname, spec_file_name=fname)

        return lsf_inputs

    def enabled(self, enable):
        """Sets tree and start button to enable.
        
        args:
            enable: bool, True for enabled False for disabled.
        """
        # ic()
        self.tree.setEnabled(enable)
        self.ui.startButton.setEnabled(enable)

    def set_mask(self, idx, mask):
        self.mask = np.arange(self.mask_widget.data.size)[mask.ravel() == 1]
        self.thread.mask = self.mask

    def launch_mask_widget(self):
        key = self.parameters.child("Detector").value()
        data = np.zeros(DETECTOR_DICT[key]["shape"])
        data[0, 0] = 1
        if self.mask is not None:
            _mask = np.zeros_like(data.T)
            _mask.ravel()[self.mask] = 1
            self.mask_widget.set_data(data.T, base=_mask)
        else:
            self.mask_widget.set_data(data.T)
        self.mask_widget.show()
Пример #5
0
class ObjectTree(QWidget, ComponentMixin):

    name = 'Object Tree'
    _stash = []

    preferences = Parameter.create(name='Preferences',
                                   children=[{
                                       'name': 'Clear all before each run',
                                       'type': 'bool',
                                       'value': True
                                   }, {
                                       'name': 'STL precision',
                                       'type': 'float',
                                       'value': .1
                                   }])

    sigObjectsAdded = pyqtSignal(list)
    sigObjectsRemoved = pyqtSignal(list)
    sigCQObjectSelected = pyqtSignal(object)
    sigItemChanged = pyqtSignal(QTreeWidgetItem, int)
    sigObjectPropertiesChanged = pyqtSignal()

    def __init__(self, parent):

        super(ObjectTree, self).__init__(parent)

        self.tree = tree = QTreeWidget(self)
        self.properties_editor = ParameterTree(self)

        tree.setHeaderHidden(True)
        tree.setItemsExpandable(False)
        tree.setRootIsDecorated(False)
        tree.setContextMenuPolicy(Qt.ActionsContextMenu)

        #forward itemChanged singal
        tree.itemChanged.connect(\
            lambda item,col: self.sigItemChanged.emit(item,col))
        #handle visibility changes form tree
        tree.itemChanged.connect(self.handleChecked)

        self.CQ = CQRootItem()
        self.Imports = ImportRootItem()
        self.Helpers = HelpersRootItem()

        root = tree.invisibleRootItem()
        root.addChild(self.CQ)
        root.addChild(self.Imports)
        root.addChild(self.Helpers)

        self._export_STL_action = \
            QAction('Export as STL',
                    self,
                    enabled=False,
                    triggered=lambda: \
                        self.export('*stl','stl',
                                    self.preferences['STL precision']))

        self._export_STEP_action = \
            QAction('Export as SETP',
                    self,
                    enabled=False,
                    triggered=lambda: \
                        self.export('*step','step',[]))

        self._clear_current_action = QAction(icon('delete'),
                                             'Clear current',
                                             self,
                                             enabled=False,
                                             triggered=self.removeSelected)

        self._toolbar_actions = \
            [QAction(icon('delete-many'),'Clear all',self,triggered=self.removeObjects),
             self._clear_current_action,]

        self.prepareMenu()

        tree.itemSelectionChanged.connect(self.handleSelection)
        tree.customContextMenuRequested.connect(self.showMenu)

        self.prepareLayout()

    def prepareMenu(self):

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)

        self._context_menu = QMenu(self)
        self._context_menu.addActions(self._toolbar_actions)
        self._context_menu.addActions(
            (self._export_STL_action, self._export_STEP_action))

    def prepareLayout(self):

        self._splitter = splitter((self.tree, self.properties_editor),
                                  stretch_factors=(2, 1),
                                  orientation=Qt.Vertical)
        layout(self, (self._splitter, ), top_widget=self)

        self._splitter.show()

    def showMenu(self, position):

        item = self.tree.selectedItems()[-1]
        if item.parent() is self.CQ:
            self._export_STL_action.setEnabled(True)
        else:
            self._export_STL_action.setEnabled(False)

        self._context_menu.exec_(self.tree.viewport().mapToGlobal(position))

    def menuActions(self):

        return {'Tools': [self._export_STL_action]}

    def toolbarActions(self):

        return self._toolbar_actions

    def addLines(self):

        origin = (0, 0, 0)
        ais_list = []

        for name, color, direction in zip(('X', 'Y', 'Z'), (RED, GREEN, BLUE),
                                          ((1, 0, 0), (0, 1, 0), (0, 0, 1))):
            line_placement = Geom_Line(
                gp_Ax1(gp_Pnt(*origin), gp_Dir(*direction)))
            line = AIS_Line(line_placement.GetHandle())
            line.SetColor(color)

            self.Helpers.addChild(ObjectTreeItem(name, ais=line))

            ais_list.append(line)

        self.sigObjectsAdded.emit(ais_list)
        self.tree.expandToDepth(1)

    @pyqtSlot(dict, bool)
    @pyqtSlot(dict)
    def addObjects(self, objects, clean=False, root=None, alpha=0.):

        if clean or self.preferences['Clear all before each run']:
            self.removeObjects()

        ais_list = []

        #if root is None:
        root = self.CQ

        #remove Vector objects
        objects_f = \
        {k:v for k,v in objects.items() if type(v.val()) not in (Vector,)}

        for name, shape in objects_f.items():
            ais = make_AIS(shape)
            ais.SetTransparency(alpha)
            ais_list.append(ais)
            root.addChild(
                ObjectTreeItem(name,
                               shape=shape,
                               ais=ais,
                               sig=self.sigObjectPropertiesChanged))

        self.sigObjectsAdded.emit(ais_list)

    @pyqtSlot(object, str, float)
    def addObject(
        self,
        obj,
        name='',
        alpha=.0,
    ):

        root = self.CQ

        ais = make_AIS(obj)
        ais.SetTransparency(alpha)

        root.addChild(
            ObjectTreeItem(name,
                           shape=obj,
                           ais=ais,
                           sig=self.sigObjectPropertiesChanged))

        self.sigObjectsAdded.emit([ais])

    @pyqtSlot(list)
    @pyqtSlot()
    def removeObjects(self, objects=None):

        if objects:
            removed_items_ais = [self.CQ.takeChild(i).ais for i in objects]
        else:
            removed_items_ais = [ch.ais for ch in self.CQ.takeChildren()]

        self.sigObjectsRemoved.emit(removed_items_ais)

    @pyqtSlot(bool)
    def stashObjects(self, action: bool):

        if action:
            self._stash = self.CQ.takeChildren()
            removed_items_ais = [ch.ais for ch in self._stash]
            self.sigObjectsRemoved.emit(removed_items_ais)
        else:
            self.removeObjects()
            self.CQ.addChildren(self._stash)
            ais_list = [el.ais for el in self._stash]
            self.sigObjectsAdded.emit(ais_list)

    @pyqtSlot()
    def removeSelected(self):

        ixs = self.tree.selectedIndexes()
        rows = [ix.row() for ix in ixs]

        self.removeObjects(rows)

    def export(self, file_wildcard, export_type, precision=None):

        item = self.tree.selectedItems()[-1]
        if item.parent() is self.CQ:
            shape = item.shape
        else:
            return

        fname, _ = QFileDialog.getSaveFileName(self, filter=file_wildcard)
        if fname is not '':
            export(shape, export_type, fname, precision)

    @pyqtSlot()
    def handleSelection(self):

        item = self.tree.selectedItems()[-1]
        if item.parent() is self.CQ:
            self._export_STL_action.setEnabled(True)
            self._export_STEP_action.setEnabled(True)
            self._clear_current_action.setEnabled(True)
            self.sigCQObjectSelected.emit(item.shape)
            self.properties_editor.setParameters(item.properties,
                                                 showTop=False)
            self.properties_editor.setEnabled(True)
        else:
            self._export_STL_action.setEnabled(False)
            self._export_STEP_action.setEnabled(False)
            self._clear_current_action.setEnabled(False)
            self.properties_editor.setEnabled(False)
            self.properties_editor.clear()

    @pyqtSlot(QTreeWidgetItem, int)
    def handleChecked(self, item, col):

        if type(item) is ObjectTreeItem:
            if item.checkState(0):
                item.properties['Visible'] = True
            else:
                item.properties['Visible'] = False