Example #1
0
    def createPositionerForm(self):
        """Creates the form inputs for the detector positioner"""
        title = FormTitle('Detector Position')
        self.main_layout.addWidget(title)
        self.position_form_group = FormGroup(FormGroup.Layout.Grid)

        for index, link in enumerate(self.detector.positioner.links):
            if link.type == Link.Type.Revolute:
                unit = 'degrees'
                offset = math.degrees(link.set_point)
                lower_limit = math.degrees(link.lower_limit)
                upper_limit = math.degrees(link.upper_limit)
            else:
                unit = 'mm'
                offset = link.set_point
                lower_limit = link.lower_limit
                upper_limit = link.upper_limit

            pretty_label = link.name.replace('_', ' ').title()
            control = FormControl(pretty_label,
                                  offset,
                                  desc=unit,
                                  required=True,
                                  number=True)
            control.form_lineedit.setDisabled(link.locked)
            if not link.ignore_limits:
                control.range(lower_limit, upper_limit)

            limits_button = create_tool_button(
                tooltip='Disable Joint Limits',
                style_name='MidToolButton',
                icon_path=path_for('limit.png'),
                checkable=True,
                status_tip=f'Ignore joint limits of {pretty_label}',
                checked=link.ignore_limits)
            limits_button.clicked.connect(self.ignoreJointLimits)
            limits_button.setProperty('link_index', index)

            control.extra = [limits_button]
            self.position_form_group.addControl(control)
            self.position_form_group.group_validation.connect(
                self.formValidation)

        self.main_layout.addWidget(self.position_form_group)
        button_layout = QtWidgets.QHBoxLayout()
        self.move_detector_button = QtWidgets.QPushButton('Move Detector')
        self.move_detector_button.clicked.connect(
            self.moveDetectorsButtonClicked)
        button_layout.addWidget(self.move_detector_button)
        button_layout.addStretch(1)
        self.main_layout.addLayout(button_layout)
Example #2
0
    def createApertureForm(self):
        """Creates the form inputs for changing the aperture size"""
        title = FormTitle(f'{self.instrument.jaws.name} Aperture Size')
        self.main_layout.addWidget(title)
        aperture = self.instrument.jaws.aperture
        upper_limit = self.instrument.jaws.aperture_upper_limit
        lower_limit = self.instrument.jaws.aperture_lower_limit
        self.aperture_form_group = FormGroup(FormGroup.Layout.Grid)
        control = FormControl('Horizontal Aperture Size',
                              aperture[0],
                              desc='mm',
                              required=True,
                              number=True)
        control.range(lower_limit[0], upper_limit[0])
        self.aperture_form_group.addControl(control)
        control = FormControl('Vertical Aperture Size',
                              aperture[1],
                              desc='mm',
                              required=True,
                              number=True)
        control.range(lower_limit[1], upper_limit[1])
        self.aperture_form_group.addControl(control)
        self.main_layout.addWidget(self.aperture_form_group)
        self.aperture_form_group.group_validation.connect(self.formValidation)
        self.main_layout.addSpacing(10)

        button_layout = QtWidgets.QHBoxLayout()
        self.change_aperture_button = QtWidgets.QPushButton(
            'Change Aperture Size')
        self.change_aperture_button.clicked.connect(
            self.changeApertureButtonClicked)
        button_layout.addWidget(self.change_aperture_button)
        button_layout.addStretch(1)
        self.main_layout.addLayout(button_layout)
Example #3
0
    def createPositionerForm(self):
        """Creates the form inputs for the jaw positioner"""
        title = FormTitle(f'{self.instrument.jaws.name} Position')
        self.main_layout.addWidget(title)
        self.position_form_group = FormGroup(FormGroup.Layout.Grid)

        for index in self.instrument.jaws.positioner.order:
            link = self.instrument.jaws.positioner.links[index]
            if link.type == Link.Type.Revolute:
                unit = 'degrees'
                offset = math.degrees(link.set_point)
                lower_limit = math.degrees(link.lower_limit)
                upper_limit = math.degrees(link.upper_limit)
            else:
                unit = 'mm'
                offset = link.set_point
                lower_limit = link.lower_limit
                upper_limit = link.upper_limit

            control = FormControl(link.name.title(),
                                  offset,
                                  desc=unit,
                                  required=True,
                                  number=True)
            control.form_lineedit.setDisabled(link.locked)
            if not link.ignore_limits:
                control.range(lower_limit, upper_limit)

            limits_button = create_tool_button(
                tooltip='Disable Joint Limits',
                style_name='MidToolButton',
                icon_path=path_for('limit.png'),
                checkable=True,
                status_tip=f'Ignore joint limits of {link.name}',
                checked=link.ignore_limits)
            limits_button.clicked.connect(self.ignoreJointLimits)
            limits_button.setProperty('link_index', index)

            control.extra = [limits_button]
            self.position_form_group.addControl(control)
            self.position_form_group.group_validation.connect(
                self.formValidation)

        self.main_layout.addWidget(self.position_form_group)
        button_layout = QtWidgets.QHBoxLayout()
        self.move_jaws_button = QtWidgets.QPushButton('Move Jaws')
        self.move_jaws_button.clicked.connect(self.moveJawsButtonClicked)
        button_layout.addWidget(self.move_jaws_button)
        button_layout.addStretch(1)
        self.main_layout.addLayout(button_layout)
Example #4
0
    def __init__(self, sample, parent):
        super().__init__()

        self.parent = parent

        self.main_layout = QtWidgets.QVBoxLayout()
        self.main_layout.setContentsMargins(0, 0, 0, 0)
        unit = 'mm'

        self.form_group = FormGroup()
        self.x_position = FormControl('X',
                                      0.0,
                                      required=True,
                                      desc=unit,
                                      number=True)
        self.y_position = FormControl('Y',
                                      0.0,
                                      required=True,
                                      desc=unit,
                                      number=True)
        self.z_position = FormControl('Z',
                                      0.0,
                                      required=True,
                                      desc=unit,
                                      number=True)
        self.form_group.addControl(self.x_position)
        self.form_group.addControl(self.y_position)
        self.form_group.addControl(self.z_position)
        self.form_group.group_validation.connect(self.formValidation)
        button_layout = QtWidgets.QHBoxLayout()
        self.execute_button = QtWidgets.QPushButton(
            TransformType.Translate.value)
        self.execute_button.clicked.connect(self.executeButtonClicked)
        button_layout.addWidget(self.execute_button)
        button_layout.addStretch(1)

        self.main_layout.addWidget(self.form_group)
        self.main_layout.addLayout(button_layout)
        self.main_layout.addStretch(1)
        self.setLayout(self.main_layout)

        self.valid_sample = False
        self.selected_sample = sample
Example #5
0
    def createCustomPlaneBox(self):
        self.custom_plane_widget = QtWidgets.QWidget(self)
        layout = QtWidgets.QVBoxLayout()

        self.form_group = FormGroup(FormGroup.Layout.Horizontal)
        self.x_axis = FormControl('X', 1.0, required=True, number=True)
        self.x_axis.range(-1.0, 1.0)
        self.y_axis = FormControl('Y', 0.0, required=True, number=True)
        self.y_axis.range(-1.0, 1.0)
        self.z_axis = FormControl('Z', 0.0, required=True, number=True)
        self.z_axis.range(-1.0, 1.0)
        self.form_group.addControl(self.x_axis)
        self.form_group.addControl(self.y_axis)
        self.form_group.addControl(self.z_axis)
        self.form_group.group_validation.connect(self.setCustomPlane)

        layout.addWidget(self.form_group)
        self.custom_plane_widget.setLayout(layout)
        self.custom_plane_widget.setVisible(False)
        self.main_layout.addWidget(self.custom_plane_widget)
Example #6
0
    def createPositionerWidget(self, positioner, add_base_button=False):
        """Creates the form widget for the given positioner

        :param positioner: serial manipulator
        :type positioner: SerialManipulator
        :param add_base_button: indicates base button should be shown
        :type add_base_button: bool
        :return: positioner widget
        :rtype: QtWidgets.QWidget
        """
        widget = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        title = FormTitle(positioner.name)
        layout.addWidget(title)
        if add_base_button:
            base_button = create_tool_button(tooltip='Change Base Matrix',
                                             style_name='MidToolButton')

            import_base_action = QtWidgets.QAction('Import Base Matrix', self)
            import_base_action.setStatusTip(
                f'Import base transformation matrix for {positioner.name}')
            import_base_action.triggered.connect(
                lambda ignore, n=positioner.name: self.importPositionerBase(n))

            compute_base_action = QtWidgets.QAction('Calculate Base Matrix',
                                                    self)
            compute_base_action.setStatusTip(
                f'Calculate base transformation matrix for {positioner.name}')
            compute_base_action.triggered.connect(
                lambda ignore, n=positioner.name: self.computePositionerBase(n
                                                                             ))

            export_base_action = QtWidgets.QAction('Export Base Matrix', self)
            export_base_action.setStatusTip(
                f'Export base transformation matrix for {positioner.name}')
            export_base_action.triggered.connect(
                lambda ignore, n=positioner.name: self.exportPositionerBase(n))

            base_button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
            base_button.addActions(
                [import_base_action, compute_base_action, export_base_action])
            base_button.setDefaultAction(import_base_action)
            base_button.setIcon(QtGui.QIcon(path_for('base.png')))
            title.addHeaderControl(base_button)

            reset_button = create_tool_button(
                tooltip='Reset Base Matrix',
                style_name='MidToolButton',
                status_tip=
                f'Reset base transformation matrix of {positioner.name}',
                icon_path=path_for('refresh.png'),
                hide=True)
            reset_button.clicked.connect(
                lambda ignore, n=positioner.name: self.resetPositionerBase(n))
            title.addHeaderControl(reset_button)
            reset_button.setVisible(
                positioner.base is not positioner.default_base)
            self.base_reset_buttons[positioner.name] = reset_button

        form_group = FormGroup(FormGroup.Layout.Grid)
        order_offset = len(self.positioner_form_controls)
        for index in positioner.order:
            link = positioner.links[index]
            if link.type == Link.Type.Revolute:
                unit = 'degrees'
                offset = math.degrees(link.set_point)
                lower_limit = math.degrees(link.lower_limit)
                upper_limit = math.degrees(link.upper_limit)
            else:
                unit = 'mm'
                offset = link.set_point
                lower_limit = link.lower_limit
                upper_limit = link.upper_limit

            control = FormControl(link.name.title(),
                                  offset,
                                  desc=unit,
                                  required=True,
                                  number=True)
            control.form_lineedit.setDisabled(link.locked)
            if not link.ignore_limits:
                control.range(lower_limit, upper_limit)

            lock_button = create_tool_button(
                tooltip='Lock Joint',
                style_name='MidToolButton',
                status_tip=f'Lock {link.name} joint in {positioner.name}',
                icon_path=path_for('lock.png'),
                checkable=True,
                checked=link.locked)
            lock_button.clicked.connect(self.lockJoint)
            lock_button.setProperty('link_index', index + order_offset)

            limits_button = create_tool_button(
                tooltip='Disable Joint Limits',
                style_name='MidToolButton',
                status_tip=f'Ignore joint limits of {link.name}',
                icon_path=path_for('limit.png'),
                checkable=True,
                checked=link.ignore_limits)
            limits_button.clicked.connect(self.ignoreJointLimits)
            limits_button.setProperty('link_index', index + order_offset)

            control.extra = [lock_button, limits_button]
            form_group.addControl(control)

            form_group.group_validation.connect(self.formValidation)
            self.positioner_form_controls.append(control)

        layout.addWidget(form_group)
        widget.setLayout(layout)
        self.positioner_forms.append(form_group)

        return widget
Example #7
0
class PlaneAlignmentTool(QtWidgets.QWidget):
    """Creates a UI to rotate a selected plane on the sample so that it is aligned with an arbitrary
    plane or a plane formed by any 2 axes of the coordinate system. The plane on the sample is specified
    by picking 3 or more points on the surface of the sample.

    :param sample: name of sample
    :type sample: str
    :param parent: main window instance
    :type parent: MainWindow
    """
    def __init__(self, sample, parent):
        super().__init__()

        self.parent = parent

        self.main_layout = QtWidgets.QVBoxLayout()
        self.main_layout.setContentsMargins(0, 0, 0, 0)
        self.vertices = None
        self.initial_plane = None
        self.final_plane_normal = None

        layout = QtWidgets.QHBoxLayout()
        self.table_widget = QtWidgets.QTableWidget()
        self.table_widget.setShowGrid(False)
        self.table_widget.setAlternatingRowColors(True)
        self.table_widget.setEditTriggers(
            QtWidgets.QAbstractItemView.NoEditTriggers)
        self.table_widget.setColumnCount(3)
        self.table_widget.setFixedHeight(150)
        self.table_widget.verticalHeader().setVisible(False)
        self.table_widget.setHorizontalHeaderLabels(['X', 'Y', 'Z'])
        self.table_widget.setSelectionBehavior(
            QtWidgets.QAbstractItemView.SelectRows)
        self.table_widget.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection)
        self.table_widget.selectionModel().selectionChanged.connect(
            self.selection)
        self.table_widget.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.Stretch)
        layout.addWidget(self.table_widget)

        button_layout = QtWidgets.QVBoxLayout()
        self.select_button = create_tool_button(
            icon_path=path_for('select.png'),
            checkable=True,
            checked=True,
            status_tip='Normal scene manipulation with the mouse',
            tooltip='Normal Mode',
            style_name='ToolButton')
        self.select_button.clicked.connect(lambda: self.togglePicking(False))
        button_layout.addWidget(self.select_button)

        self.pick_button = create_tool_button(
            icon_path=path_for('point.png'),
            checkable=True,
            status_tip='Select 3D points that define the plane',
            tooltip='Pick Point Mode',
            style_name='ToolButton')

        self.pick_button.clicked.connect(lambda: self.togglePicking(True))
        button_layout.addWidget(self.pick_button)

        self.delete_button = create_tool_button(
            icon_path=path_for('cross.png'),
            style_name='ToolButton',
            status_tip='Remove selected points from the scene',
            tooltip='Delete Points')
        self.delete_button.clicked.connect(self.removePicks)
        button_layout.addWidget(self.delete_button)

        layout.addSpacing(10)
        layout.addLayout(button_layout)
        self.main_layout.addLayout(layout)
        self.main_layout.addSpacing(10)
        self.main_layout.addWidget(QtWidgets.QLabel('Select Final Plane:'))
        self.plane_combobox = QtWidgets.QComboBox()
        self.plane_combobox.setView(QtWidgets.QListView())
        self.plane_combobox.addItems([p.value for p in PlaneOptions])
        self.plane_combobox.currentTextChanged.connect(self.setPlane)
        self.main_layout.addWidget(self.plane_combobox)
        self.createCustomPlaneBox()
        self.setPlane(self.plane_combobox.currentText())

        button_layout = QtWidgets.QHBoxLayout()
        self.execute_button = QtWidgets.QPushButton('Align Planes')
        self.execute_button.clicked.connect(self.executeButtonClicked)
        button_layout.addWidget(self.execute_button)
        button_layout.addStretch(1)

        self.main_layout.addLayout(button_layout)
        self.main_layout.addStretch(1)

        self.setLayout(self.main_layout)
        self.parent.gl_widget.pick_added.connect(self.addPicks)

        self.selected_sample = sample

    def togglePicking(self, value):
        """Toggles between point picking and scene manipulation in the graphics widget

        :param value: indicates if picking is enabled
        :type value: bool
        """
        self.parent.gl_widget.picking = value
        if value:
            self.select_button.setChecked(False)
            self.pick_button.setChecked(True)
        else:
            self.select_button.setChecked(True)
            self.pick_button.setChecked(False)

    def createCustomPlaneBox(self):
        self.custom_plane_widget = QtWidgets.QWidget(self)
        layout = QtWidgets.QVBoxLayout()

        self.form_group = FormGroup(FormGroup.Layout.Horizontal)
        self.x_axis = FormControl('X', 1.0, required=True, number=True)
        self.x_axis.range(-1.0, 1.0)
        self.y_axis = FormControl('Y', 0.0, required=True, number=True)
        self.y_axis.range(-1.0, 1.0)
        self.z_axis = FormControl('Z', 0.0, required=True, number=True)
        self.z_axis.range(-1.0, 1.0)
        self.form_group.addControl(self.x_axis)
        self.form_group.addControl(self.y_axis)
        self.form_group.addControl(self.z_axis)
        self.form_group.group_validation.connect(self.setCustomPlane)

        layout.addWidget(self.form_group)
        self.custom_plane_widget.setLayout(layout)
        self.custom_plane_widget.setVisible(False)
        self.main_layout.addWidget(self.custom_plane_widget)

    def setCustomPlane(self, is_valid):
        """Sets custom destination/final plane normal. Error is shown if normal is invalid

        :param is_valid: indicate if the custom plane normal is valid
        :type is_valid: bool
        """
        if is_valid:
            normal = Vector3(
                [self.x_axis.value, self.y_axis.value, self.z_axis.value])
            length = normal.length
            if length > 1e-5:
                self.final_plane_normal = normal / length
                self.x_axis.validation_label.setText('')
                return
            else:
                self.x_axis.validation_label.setText('Bad Normal')

        self.final_plane_normal = None

    def setPlane(self, selected_text):
        """Sets destination/final plane normal or shows inputs for custom normal

        :param selected_text: PlaneOptions
        :type selected_text: str
        """
        if selected_text == PlaneOptions.Custom.value:
            self.custom_plane_widget.setVisible(True)
            return
        elif selected_text == PlaneOptions.XY.value:
            self.final_plane_normal = Vector3([0., 0., 1.])
        elif selected_text == PlaneOptions.XZ.value:
            self.final_plane_normal = Vector3([0., 1., 0.])
        else:
            self.final_plane_normal = Vector3([1., 0., 0.])

        self.custom_plane_widget.setVisible(False)

    @property
    def selected_sample(self):
        """Gets and sets the selected sample key

        :returns: selected sample key
        :rtype: str
        """
        return self._selected_sample

    @selected_sample.setter
    def selected_sample(self, value):
        self._selected_sample = value

        sample = self.parent.presenter.model.sample
        if not sample:
            self.vertices = None
            self.execute_button.setDisabled(True)
            self.clearPicks()
            return

        if value is None:
            mesh = None
            for s in sample.values():
                if mesh is None:
                    mesh = s.copy()
                else:
                    mesh.append(s)
        else:
            mesh = self.parent.presenter.model.sample[value]

        self.vertices = mesh.vertices[mesh.indices].reshape(-1, 9)
        self.plane_size = mesh.bounding_box.radius
        self.sample_center = mesh.bounding_box.center
        self.execute_button.setEnabled(True)

    def selection(self):
        """Highlights selected picks in the graphics widget"""
        picks = self.parent.gl_widget.picks
        sm = self.table_widget.selectionModel()
        for i in range(self.table_widget.rowCount()):
            picks[i][1] = sm.isRowSelected(i, QtCore.QModelIndex())
        self.parent.gl_widget.update()

    def addPicks(self, start, end):
        """Computes pick coordinates and adds pick to table and graphics widgets. Pick coordinates are
        computed as the first intersection between start and end coordinates

        :param start: start point of pick line segment
        :type start: Vector3
        :param end: end point of pick line segment
        :type end: Vector3
        """
        points = point_selection(start, end, self.vertices)
        if points.size == 0:
            return

        point = points[0]

        last_index = self.table_widget.rowCount()
        self.table_widget.insertRow(last_index)
        for i in range(3):
            item = QtWidgets.QTableWidgetItem(f'{point[i]:.3f}')
            item.setTextAlignment(QtCore.Qt.AlignCenter)
            self.table_widget.setItem(last_index, i, item)
        self.parent.gl_widget.picks.append([list(point), False])
        self.updateInitialPlane()

    def removePicks(self):
        """Removes picks selected in the table widget and updates the initial plane"""
        model_index = [
            m.row() for m in self.table_widget.selectionModel().selectedRows()
        ]
        model_index.sort(reverse=True)
        self.table_widget.selectionModel().reset()
        for index in model_index:
            del self.parent.gl_widget.picks[index]
            self.table_widget.removeRow(index)

        self.updateInitialPlane()

    def updateInitialPlane(self):
        """Computes the initial plane when 3 or more points have been picked"""
        if len(self.parent.gl_widget.picks) > 2:
            points = list(zip(*self.parent.gl_widget.picks))[0]
            self.initial_plane = Plane.fromBestFit(points)
            d = self.initial_plane.normal.dot(self.initial_plane.point -
                                              self.sample_center)
            self.initial_plane.point = self.sample_center + self.initial_plane.normal * d
            self.parent.scenes.drawPlane(self.initial_plane,
                                         2 * self.plane_size,
                                         2 * self.plane_size)
        else:
            self.initial_plane = None
            self.parent.scenes.removePlane()

    def closeEvent(self, event):
        self.parent.gl_widget.picking = False
        self.clearPicks()
        event.accept()

    def executeButtonClicked(self):
        if self.final_plane_normal is None or self.initial_plane is None:
            return

        matrix = Matrix44.identity()
        matrix[0:3, 0:3] = rotation_btw_vectors(self.initial_plane.normal,
                                                self.final_plane_normal)
        if not is_close(matrix, Matrix44.identity()):
            self.parent.presenter.transformSample(matrix, self.selected_sample,
                                                  TransformType.Custom)
            self.clearPicks()

    def clearPicks(self):
        """Clears picks from tool and graphics widget"""
        self.table_widget.clear()
        self.initial_plane = None
        self.parent.gl_widget.picks.clear()
        self.parent.scenes.removePlane()
Example #8
0
class RotateTool(QtWidgets.QWidget):
    """Creates a UI for applying simple rotation around the 3 principal axes

    :param sample: name of sample
    :type sample: str
    :param parent: main window instance
    :type parent: MainWindow
    """
    def __init__(self, sample, parent):
        super().__init__()

        self.parent = parent

        self.main_layout = QtWidgets.QVBoxLayout()
        self.main_layout.setContentsMargins(0, 0, 0, 0)
        unit = 'degrees'

        self.form_group = FormGroup()
        self.x_rotation = FormControl('X',
                                      0.0,
                                      required=True,
                                      desc=unit,
                                      number=True)
        self.x_rotation.range(-360.0, 360.0)
        self.y_rotation = FormControl('Y',
                                      0.0,
                                      required=True,
                                      desc=unit,
                                      number=True)
        self.y_rotation.range(-360.0, 360.0)
        self.z_rotation = FormControl('Z',
                                      0.0,
                                      required=True,
                                      desc=unit,
                                      number=True)
        self.z_rotation.range(-360.0, 360.0)
        self.form_group.addControl(self.x_rotation)
        self.form_group.addControl(self.y_rotation)
        self.form_group.addControl(self.z_rotation)
        self.form_group.group_validation.connect(self.formValidation)
        button_layout = QtWidgets.QHBoxLayout()
        self.execute_button = QtWidgets.QPushButton(TransformType.Rotate.value)
        self.execute_button.clicked.connect(self.executeButtonClicked)
        button_layout.addWidget(self.execute_button)
        button_layout.addStretch(1)

        self.main_layout.addWidget(self.form_group)
        self.main_layout.addLayout(button_layout)
        self.main_layout.addStretch(1)
        self.setLayout(self.main_layout)

        self.valid_sample = False
        self.selected_sample = sample

    @property
    def selected_sample(self):
        """Gets and sets the selected sample key

        :returns: selected sample key
        :rtype: str
        """
        return self._selected_sample

    @selected_sample.setter
    def selected_sample(self, value):
        self._selected_sample = value
        self.valid_sample = True if self.parent.presenter.model.sample else False
        self.form_group.validateGroup()

    def formValidation(self, is_valid):
        if is_valid and self.valid_sample:
            self.execute_button.setEnabled(True)
        else:
            self.execute_button.setDisabled(True)

    def executeButtonClicked(self):
        angles = [
            self.z_rotation.value, self.y_rotation.value, self.x_rotation.value
        ]
        if not is_close(angles, [0.0, 0.0, 0.0]):
            self.parent.presenter.transformSample(angles, self.selected_sample,
                                                  TransformType.Rotate)